@maudecode/talaria-web 1.17.2 → 1.18.0

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 (387) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.d.ts +1 -1
  3. package/dist/api/auth-raw.js +4 -4
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +6 -0
  6. package/dist/api/automation-raw.js +53 -6
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +26 -1
  9. package/dist/api/automation-router.js +16 -2
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +649 -15
  12. package/dist/api/chat-router.js +52 -50
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +35 -1
  15. package/dist/api/git-router.js +18 -19
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +8 -0
  18. package/dist/api/raw-routes.js +247 -36
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +10 -0
  21. package/dist/api/router.js +10 -8
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/sessions-router.d.ts +1492 -115
  24. package/dist/api/sessions-router.js +134 -8
  25. package/dist/api/sessions-router.js.map +1 -1
  26. package/dist/api/settings-router.d.ts +31 -0
  27. package/dist/api/settings-router.js +8 -3
  28. package/dist/api/settings-router.js.map +1 -1
  29. package/dist/api/sse-routes.d.ts +5 -0
  30. package/dist/api/sse-routes.js +50 -3
  31. package/dist/api/sse-routes.js.map +1 -1
  32. package/dist/api/tools-raw.js +6 -23
  33. package/dist/api/tools-raw.js.map +1 -1
  34. package/dist/api/tools-router.d.ts +356 -15
  35. package/dist/api/tools-router.js +148 -19
  36. package/dist/api/tools-router.js.map +1 -1
  37. package/dist/app.js +35 -15
  38. package/dist/app.js.map +1 -1
  39. package/dist/auth/gate.js +5 -2
  40. package/dist/auth/gate.js.map +1 -1
  41. package/dist/auth/oidc.d.ts +5 -2
  42. package/dist/auth/oidc.js +8 -7
  43. package/dist/auth/oidc.js.map +1 -1
  44. package/dist/bin/talaria-web.js +1 -1
  45. package/dist/bin/talaria-web.js.map +1 -1
  46. package/dist/cli/dotenv.js +1 -0
  47. package/dist/cli/dotenv.js.map +1 -1
  48. package/dist/config/agent-config.js +3 -2
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/config.d.ts +2 -0
  51. package/dist/config.js +5 -1
  52. package/dist/config.js.map +1 -1
  53. package/dist/http/context.d.ts +17 -3
  54. package/dist/http/context.js +31 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/http/csp.d.ts +2 -1
  57. package/dist/http/csp.js +3 -2
  58. package/dist/http/csp.js.map +1 -1
  59. package/dist/profiles/profiles.d.ts +10 -0
  60. package/dist/profiles/profiles.js +40 -2
  61. package/dist/profiles/profiles.js.map +1 -1
  62. package/dist/providers/catalog.d.ts +6 -2
  63. package/dist/providers/catalog.js +30 -11
  64. package/dist/providers/catalog.js.map +1 -1
  65. package/dist/raster-image.d.ts +7 -0
  66. package/dist/raster-image.js +267 -0
  67. package/dist/raster-image.js.map +1 -0
  68. package/dist/redact.js +327 -40
  69. package/dist/redact.js.map +1 -1
  70. package/dist/runtime.js +89 -23
  71. package/dist/runtime.js.map +1 -1
  72. package/dist/server.js +5 -0
  73. package/dist/server.js.map +1 -1
  74. package/dist/sessions/background-tasks.d.ts +2 -9
  75. package/dist/sessions/background-tasks.js +2 -12
  76. package/dist/sessions/background-tasks.js.map +1 -1
  77. package/dist/sessions/cli-sessions.d.ts +15 -4
  78. package/dist/sessions/cli-sessions.js +120 -29
  79. package/dist/sessions/cli-sessions.js.map +1 -1
  80. package/dist/sessions/completions.d.ts +48 -8
  81. package/dist/sessions/completions.js +222 -26
  82. package/dist/sessions/completions.js.map +1 -1
  83. package/dist/sessions/export.d.ts +5 -0
  84. package/dist/sessions/export.js +30 -0
  85. package/dist/sessions/export.js.map +1 -1
  86. package/dist/sessions/gateway-watcher.d.ts +7 -3
  87. package/dist/sessions/gateway-watcher.js +31 -19
  88. package/dist/sessions/gateway-watcher.js.map +1 -1
  89. package/dist/sessions/handoff.d.ts +32 -0
  90. package/dist/sessions/handoff.js +114 -0
  91. package/dist/sessions/handoff.js.map +1 -0
  92. package/dist/sessions/list.d.ts +7 -1
  93. package/dist/sessions/list.js +13 -5
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +2 -4
  96. package/dist/sessions/merge.js +8 -15
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/recovery.d.ts +22 -0
  99. package/dist/sessions/recovery.js +506 -0
  100. package/dist/sessions/recovery.js.map +1 -0
  101. package/dist/sessions/relay.d.ts +21 -2
  102. package/dist/sessions/relay.js +156 -11
  103. package/dist/sessions/relay.js.map +1 -1
  104. package/dist/sessions/service.d.ts +108 -6
  105. package/dist/sessions/service.js +403 -29
  106. package/dist/sessions/service.js.map +1 -1
  107. package/dist/sessions/session.d.ts +6 -0
  108. package/dist/sessions/session.js +40 -2
  109. package/dist/sessions/session.js.map +1 -1
  110. package/dist/sessions/state-db-worker.d.ts +1 -0
  111. package/dist/sessions/state-db-worker.js +33 -0
  112. package/dist/sessions/state-db-worker.js.map +1 -0
  113. package/dist/sessions/state-db.d.ts +61 -0
  114. package/dist/sessions/state-db.js +291 -0
  115. package/dist/sessions/state-db.js.map +1 -1
  116. package/dist/sessions/store.d.ts +7 -0
  117. package/dist/sessions/store.js +31 -22
  118. package/dist/sessions/store.js.map +1 -1
  119. package/dist/sessions/titles.d.ts +2 -0
  120. package/dist/sessions/titles.js +4 -0
  121. package/dist/sessions/titles.js.map +1 -1
  122. package/dist/sessions/tool-display.d.ts +3 -3
  123. package/dist/sessions/tool-display.js +11 -37
  124. package/dist/sessions/tool-display.js.map +1 -1
  125. package/dist/sessions/turn-context.d.ts +1 -1
  126. package/dist/sessions/turn-context.js +11 -7
  127. package/dist/sessions/turn-context.js.map +1 -1
  128. package/dist/sessions/turn.d.ts +37 -2
  129. package/dist/sessions/turn.js +182 -49
  130. package/dist/sessions/turn.js.map +1 -1
  131. package/dist/sessions/wakeup-pause.d.ts +59 -0
  132. package/dist/sessions/wakeup-pause.js +181 -0
  133. package/dist/sessions/wakeup-pause.js.map +1 -0
  134. package/dist/settings.js +5 -7
  135. package/dist/settings.js.map +1 -1
  136. package/dist/sidecar/fake.d.ts +2 -2
  137. package/dist/sidecar/fake.js.map +1 -1
  138. package/dist/startup.d.ts +13 -5
  139. package/dist/startup.js +48 -0
  140. package/dist/startup.js.map +1 -1
  141. package/dist/test/ts-worker-hooks.d.ts +1 -0
  142. package/dist/test/ts-worker-hooks.js +24 -0
  143. package/dist/test/ts-worker-hooks.js.map +1 -0
  144. package/dist/text/diff.d.ts +9 -14
  145. package/dist/text/diff.js +74 -108
  146. package/dist/text/diff.js.map +1 -1
  147. package/dist/tools/crons.js +45 -8
  148. package/dist/tools/crons.js.map +1 -1
  149. package/dist/tools/dashboard-plugins.d.ts +53 -0
  150. package/dist/tools/dashboard-plugins.js +188 -0
  151. package/dist/tools/dashboard-plugins.js.map +1 -0
  152. package/dist/tools/extensions.d.ts +13 -1
  153. package/dist/tools/extensions.js +132 -60
  154. package/dist/tools/extensions.js.map +1 -1
  155. package/dist/tools/health.d.ts +32 -0
  156. package/dist/tools/health.js +111 -19
  157. package/dist/tools/health.js.map +1 -1
  158. package/dist/tools/mcp.d.ts +13 -0
  159. package/dist/tools/mcp.js +88 -0
  160. package/dist/tools/mcp.js.map +1 -1
  161. package/dist/tools/project-os.d.ts +11 -0
  162. package/dist/tools/project-os.js +267 -0
  163. package/dist/tools/project-os.js.map +1 -0
  164. package/dist/tools/skills.d.ts +5 -1
  165. package/dist/tools/skills.js +5 -3
  166. package/dist/tools/skills.js.map +1 -1
  167. package/dist/tools/updates.d.ts +27 -9
  168. package/dist/tools/updates.js +163 -51
  169. package/dist/tools/updates.js.map +1 -1
  170. package/dist/tools/wiki.d.ts +68 -0
  171. package/dist/tools/wiki.js +413 -0
  172. package/dist/tools/wiki.js.map +1 -0
  173. package/dist/workspace/escape.d.ts +61 -0
  174. package/dist/workspace/escape.js +149 -0
  175. package/dist/workspace/escape.js.map +1 -0
  176. package/dist/workspace/extract.d.ts +22 -0
  177. package/dist/workspace/extract.js +308 -0
  178. package/dist/workspace/extract.js.map +1 -0
  179. package/dist/workspace/fs.d.ts +12 -0
  180. package/dist/workspace/fs.js +92 -23
  181. package/dist/workspace/fs.js.map +1 -1
  182. package/dist/workspace/git.d.ts +18 -0
  183. package/dist/workspace/git.js +38 -25
  184. package/dist/workspace/git.js.map +1 -1
  185. package/dist/workspace/media.d.ts +4 -4
  186. package/dist/workspace/media.js +16 -45
  187. package/dist/workspace/media.js.map +1 -1
  188. package/dist/workspace/rollback.d.ts +34 -20
  189. package/dist/workspace/rollback.js +171 -265
  190. package/dist/workspace/rollback.js.map +1 -1
  191. package/dist/workspace/unzip.js +4 -1
  192. package/dist/workspace/unzip.js.map +1 -1
  193. package/dist/workspace/upload.d.ts +7 -4
  194. package/dist/workspace/upload.js +5 -2
  195. package/dist/workspace/upload.js.map +1 -1
  196. package/dist/workspace/worktrees.d.ts +2 -1
  197. package/dist/workspace/worktrees.js +14 -15
  198. package/dist/workspace/worktrees.js.map +1 -1
  199. package/package.json +4 -2
  200. package/sidecar/agent_dependency.json +4 -4
  201. package/sidecar/scripts/replay_sidecar.py +24 -0
  202. package/sidecar/talaria_sidecar/__init__.py +1 -1
  203. package/sidecar/talaria_sidecar/home.py +13 -2
  204. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  205. package/sidecar/talaria_sidecar/methods/aux.py +39 -5
  206. package/sidecar/talaria_sidecar/methods/chat.py +9 -5
  207. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  208. package/sidecar/talaria_sidecar/methods/commands.py +81 -0
  209. package/sidecar/talaria_sidecar/methods/cron.py +29 -2
  210. package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
  211. package/sidecar/talaria_sidecar/methods/kanban.py +28 -7
  212. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  213. package/sidecar/talaria_sidecar/methods/process.py +58 -4
  214. package/sidecar/talaria_sidecar/methods/profiles.py +77 -7
  215. package/sidecar/talaria_sidecar/methods/providers.py +27 -8
  216. package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
  217. package/sidecar/talaria_sidecar/runtime.py +17 -1
  218. package/static/dist/FILES.txt +127 -126
  219. package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
  220. package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
  221. package/static/dist/assets/Dialog-g355qxhp.js +1 -0
  222. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
  223. package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
  224. package/static/dist/assets/{Markdown-mdwlvj3w.js → Markdown-j4kut4j2.js} +114 -114
  225. package/static/dist/assets/{RightPanel-ftvrjlcp.js → RightPanel-ohlsdzfl.js} +1 -1
  226. package/static/dist/assets/Select-kddq75qc.js +1 -0
  227. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
  228. package/static/dist/assets/{SettingsLayout-jonjefmj.js → SettingsLayout-o9bv4mgb.js} +1 -1
  229. package/static/dist/assets/{States-nmejrwrm.js → States-eq6fe29m.js} +1 -1
  230. package/static/dist/assets/Toaster-pc247u9f.js +1 -0
  231. package/static/dist/assets/{_app-gdidzd94.js → _app-f8n1i0m1.js} +1 -1
  232. package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-gfbzpoou.js} +1 -1
  233. package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
  234. package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
  235. package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
  236. package/static/dist/assets/_app.logs-i69qds98.js +1 -0
  237. package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
  238. package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
  239. package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-fn1bhde6.js} +1 -1
  240. package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
  241. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
  242. package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
  243. package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
  244. package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
  245. package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
  246. package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
  247. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
  248. package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
  249. package/static/dist/assets/{back-bemaer09.js → back-h7aun7ew.js} +1 -1
  250. package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
  251. package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
  252. package/static/dist/assets/{cancel-drd0g6d9.js → cancel-fhi0vcnu.js} +1 -1
  253. package/static/dist/assets/channel-b2zo73z1.js +1 -0
  254. package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
  255. package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
  256. package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
  257. package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
  258. package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
  259. package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
  260. package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
  261. package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
  262. package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
  263. package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
  264. package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
  265. package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
  266. package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
  267. package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-dnau702j.js} +1 -1
  268. package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
  269. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
  270. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
  271. package/static/dist/assets/{copied-ima3pysb.js → copied-hcplv46p.js} +1 -1
  272. package/static/dist/assets/{copy-fw5b1pc6.js → copy-magtofyp.js} +1 -1
  273. package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
  274. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
  275. package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
  276. package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
  277. package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
  278. package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
  279. package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
  280. package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
  281. package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
  282. package/static/dist/assets/endpoints-klbxod4t.js +64 -0
  283. package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
  284. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
  285. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
  286. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
  287. package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
  288. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
  289. package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
  290. package/static/dist/assets/host-i828xuo7.js +1 -0
  291. package/static/dist/assets/index-eh8zilnv.css +2 -0
  292. package/static/dist/assets/{index-cxq58xb1.js → index-r648f4o1.js} +5 -5
  293. package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
  294. package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
  295. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
  296. package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
  297. package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
  298. package/static/dist/assets/{lazyRouteComponent-af0lvgjx.js → lazyRouteComponent-e1y4t2i9.js} +1 -1
  299. package/static/dist/assets/{line-osxq6fq7.js → line-meizqsvn.js} +1 -1
  300. package/static/dist/assets/{link-jyxicdhg.js → link-oot2yn73.js} +1 -1
  301. package/static/dist/assets/login-gu28l9ou.js +1 -0
  302. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
  303. package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-ntne45i9.js} +3 -3
  304. package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
  305. package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
  306. package/static/dist/assets/{onboarding_model_label-dp7xffoh.js → onboarding_model_label-ftpjdwqk.js} +1 -1
  307. package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
  308. package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
  309. package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
  310. package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
  311. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
  312. package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
  313. package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
  314. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
  315. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
  316. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
  317. package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
  318. package/static/dist/assets/{refresh-b4uams1i.js → refresh-abtu93ke.js} +1 -1
  319. package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-hlwb02qv.js} +1 -1
  320. package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
  321. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
  322. package/static/dist/assets/{saved-kg52csy7.js → saved-girnabkf.js} +1 -1
  323. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
  324. package/static/dist/assets/{settings_section_extensions_title-ej0iapxe.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
  325. package/static/dist/assets/share._token-kl0r7ido.js +2 -0
  326. package/static/dist/assets/{sliders-horizontal-fj18it47.js → sliders-horizontal-n1zm34qq.js} +1 -1
  327. package/static/dist/assets/sse-iwku0sjo.js +1 -0
  328. package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
  329. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
  330. package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
  331. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
  332. package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
  333. package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
  334. package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
  335. package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
  336. package/static/dist/assets/{tts-lxdxnmjd.js → units-kgxzxfhi.js} +1 -1
  337. package/static/dist/assets/{untitled-ovpfthg4.js → untitled-cikfjntn.js} +1 -1
  338. package/static/dist/assets/{useForm-bcblv1rx.js → useForm-kqoqg0k9.js} +1 -1
  339. package/static/dist/assets/useLocale-o59tbiea.js +1 -0
  340. package/static/dist/assets/{useParams-lhg566ew.js → useParams-b4dq6n4q.js} +1 -1
  341. package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-ipfmv3he.js} +1 -1
  342. package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
  343. package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
  344. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
  345. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
  346. package/static/dist/index.html +1 -1
  347. package/static/dist/sw.js +1 -1
  348. package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
  349. package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
  350. package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
  351. package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
  352. package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
  353. package/static/dist/assets/Select-bilaypej.js +0 -1
  354. package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
  355. package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
  356. package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
  357. package/static/dist/assets/_app.insights-luywlarz.js +0 -1
  358. package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
  359. package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
  360. package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
  361. package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
  362. package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
  363. package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
  364. package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
  365. package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
  366. package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
  367. package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
  368. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
  369. package/static/dist/assets/channel-cpdgf867.js +0 -1
  370. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
  371. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
  372. package/static/dist/assets/endpoints-d33siokx.js +0 -64
  373. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
  374. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
  375. package/static/dist/assets/index-hgtx08n5.css +0 -2
  376. package/static/dist/assets/login-ec21t39x.js +0 -1
  377. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
  378. package/static/dist/assets/onboarding-btnltvyz.js +0 -1
  379. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
  380. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
  381. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
  382. package/static/dist/assets/share._token-f97e94r0.js +0 -2
  383. package/static/dist/assets/sse-fwpqtzbg.js +0 -1
  384. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
  385. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
  386. package/static/dist/assets/todoStore-eginf9h4.js +0 -1
  387. package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
@@ -0,0 +1,188 @@
1
+ /**
2
+ * WebUI dashboard plugins (Python `api/plugins.py`): `<plugins>/<name>/dashboard/manifest.json` discovery, the
3
+ * Settings enable gate (`dashboard_plugins`, default off), and the bytes behind `/plugins/plugin.css`, the sandboxed
4
+ * panel document, plugin static assets, and tab pages. A disabled or unknown plugin serves nothing (null → 404).
5
+ */
6
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
7
+ import { escapeHtml } from '../text/markdown.js';
8
+ import { htmlWithHeadTag } from '../workspace/media.js';
9
+ import { join, resolve } from 'node:path';
10
+ import { isDict } from '../config/agent-config.js';
11
+ import { pyBool } from '../settings.js';
12
+ import { baseHrefFor, isServerOwned, isSpaPath } from '../spa.js';
13
+ import { ExtensionError, readContainedFile, text } from './extensions.js';
14
+ const NAME_RE = /^[a-z][a-z0-9_-]{0,63}$/;
15
+ /** A clean same-origin path: one leading `/` (never `//`), no quotes, whitespace, query, or fragment. */
16
+ const TAB_PATH_RE = /^\/(?!\/)[A-Za-z0-9._~/-]{0,255}$/;
17
+ const PANEL_RE = /^\/dashboard-plugins\/([a-z][a-z0-9_-]{0,63})\/index\.html$/;
18
+ const SHARED_ASSETS = new Set(['plugin.css']);
19
+ const ASSET_MIME = {
20
+ '.js': 'application/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.html': 'text/html; charset=utf-8',
21
+ '.png': 'image/png', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
22
+ '.gif': 'image/gif', '.webp': 'image/webp', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.otf': 'font/otf',
23
+ '.map': 'application/json; charset=utf-8', '.txt': 'text/plain; charset=utf-8',
24
+ };
25
+ /** Add `<base href>` unless the document declares its own. */
26
+ function withBase(body, href) {
27
+ return /<base\b/i.test(body.toString('utf8')) ? body : htmlWithHeadTag(body, `<base href="${escapeHtml(href)}">`);
28
+ }
29
+ const html = (body) => ({ body: Buffer.isBuffer(body) ? body : Buffer.from(body, 'utf8'), contentType: ASSET_MIME['.html'] ?? '' });
30
+ export class DashboardPlugins {
31
+ deps;
32
+ constructor(deps) {
33
+ this.deps = deps;
34
+ }
35
+ base() {
36
+ const raw = (this.deps.env.HERMES_WEBUI_PLUGINS_DIR ?? '').trim();
37
+ return resolve(raw || join(this.deps.hermesHome, 'plugins'));
38
+ }
39
+ /**
40
+ * Every valid plugin in name order; the first claimant of a name or tab path wins. A tab path the SPA or a
41
+ * server-owned prefix answers is refused, so a plugin can never shadow the app or `/api`.
42
+ * ponytail: rescans per call (a readdir plus small manifest reads); cache by directory mtime if plugin routes get hot.
43
+ */
44
+ list() {
45
+ let entries;
46
+ try {
47
+ entries = readdirSync(this.base()).sort();
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ const out = [];
53
+ for (const entry of entries) {
54
+ const dir = join(this.base(), entry, 'dashboard');
55
+ let manifest;
56
+ try {
57
+ if (!statSync(dir).isDirectory())
58
+ continue;
59
+ manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8'));
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (!isDict(manifest))
65
+ continue;
66
+ // Python `manifest.get("name") or entry.name` and `tab.get("path", f"/{name}")`.
67
+ const name = manifest.name || entry;
68
+ if (typeof name !== 'string' || !NAME_RE.test(name))
69
+ continue;
70
+ const tab = isDict(manifest.tab) ? manifest.tab : {};
71
+ const rawTabPath = 'path' in tab ? tab.path : `/${name}`;
72
+ // Request paths arrive without a trailing slash, so the stored tab path drops it too.
73
+ const tabPath = typeof rawTabPath === 'string' ? rawTabPath.replace(/(?<=.)\/+$/, '') : rawTabPath;
74
+ if (typeof tabPath !== 'string' || !TAB_PATH_RE.test(tabPath) || isServerOwned(tabPath) || isSpaPath(tabPath))
75
+ continue;
76
+ if (out.some((p) => p.name === name || p.tabPath === tabPath))
77
+ continue;
78
+ out.push({ name, manifest, tabPath, root: dir });
79
+ }
80
+ return out;
81
+ }
82
+ /** Python `_dashboard_plugin_enabled`: opt-in per plugin under `dashboard_plugins` in settings.json. */
83
+ enabled(name) {
84
+ try {
85
+ const prefs = this.deps.settings.load().dashboard_plugins;
86
+ return isDict(prefs) && pyBool(prefs[name]);
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
92
+ /** The Settings > Extensions switch: `SettingsStore.save` merges this one key into `dashboard_plugins`. */
93
+ async setEnabled(name, enabled) {
94
+ if (typeof enabled !== 'boolean')
95
+ throw new ExtensionError('enabled must be a boolean', 400);
96
+ await this.deps.settings.save({ dashboard_plugins: { [name]: enabled } });
97
+ }
98
+ enabledPlugin(name) {
99
+ if (!this.enabled(name))
100
+ return null;
101
+ return this.list().find((p) => p.name === name) ?? null;
102
+ }
103
+ /** Python `_from_dashboard_plugin`: one panel entry per plugin for `/api/extensions/manifests`. */
104
+ manifests() {
105
+ return this.list().map(({ name, manifest }) => {
106
+ const tab = isDict(manifest.tab) ? manifest.tab : {};
107
+ return {
108
+ id: name, name: text(manifest.label || manifest.name, 80) || name, version: text(manifest.version, 40), description: text(manifest.description, 300),
109
+ source: 'plugin', enabled: this.enabled(name), can_toggle: true, panel: `dashboard-plugins/${name}/index.html`,
110
+ nav: { label: text(tab.name || manifest.label || manifest.name, 40) || name },
111
+ capabilities: ['settings', 'storage', 'toast', 'session'], permissions: {}, settings_schema: [], theme: null, tts: null, sidecar: null, legacy_injection: false, warnings: [],
112
+ };
113
+ });
114
+ }
115
+ /** `/plugins/<rel>`: only the shared `plugin.css` under the plugin base. */
116
+ sharedAsset(rel) {
117
+ if (!SHARED_ASSETS.has(rel))
118
+ return null;
119
+ const found = readContainedFile(this.base(), rel);
120
+ return found ? { body: found.body, contentType: ASSET_MIME['.css'] ?? '' } : null;
121
+ }
122
+ /**
123
+ * Python `serve_plugin_static`: built assets under `dist/` or `static/` with an allowlisted extension, no dotfiles.
124
+ * Any other path resolves inside `dist/`, so a `dist/index.html` served as the panel can use its own relative URLs.
125
+ */
126
+ asset(name, rel) {
127
+ const plugin = this.enabledPlugin(name);
128
+ if (!plugin)
129
+ return null;
130
+ const top = rel.split('/')[0];
131
+ return this.builtAsset(plugin, top === 'dist' || top === 'static' ? rel : `dist/${rel}`);
132
+ }
133
+ builtAsset(plugin, rel) {
134
+ const top = rel.split('/')[0];
135
+ if (top !== 'dist' && top !== 'static')
136
+ return null;
137
+ const ext = rel.slice(rel.lastIndexOf('.')).toLowerCase();
138
+ const contentType = rel.includes('.') ? ASSET_MIME[ext] : undefined;
139
+ if (!contentType)
140
+ return null;
141
+ const found = readContainedFile(plugin.root, rel);
142
+ return found ? { body: found.body, contentType } : null;
143
+ }
144
+ /** `/dashboard-plugins/<name>/index.html` → plugin name, else null. */
145
+ static panelName(path) {
146
+ return PANEL_RE.exec(path)?.[1] ?? null;
147
+ }
148
+ /**
149
+ * Python `_serve_dashboard_plugin_panel`: `dist/index.html` as-is, or for a legacy IIFE plugin (`dist/index.js`,
150
+ * optional `dist/style.css`) a wrapper that mounts it beside the extension SDK.
151
+ */
152
+ panel(name) {
153
+ const plugin = this.enabledPlugin(name);
154
+ if (!plugin)
155
+ return null;
156
+ const own = this.builtAsset(plugin, 'dist/index.html');
157
+ if (own)
158
+ return own;
159
+ if (!this.builtAsset(plugin, 'dist/index.js'))
160
+ return null;
161
+ const css = this.builtAsset(plugin, 'dist/style.css') ? '<link rel="stylesheet" href="dist/style.css">' : '';
162
+ return html('<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' +
163
+ `<title>${escapeHtml(text(plugin.manifest.label, 80) || name)}</title>${css}</head><body><div id="root"></div><div id="app"></div>` +
164
+ '<script src="../../static/dist/extension-sdk.js"></script><script src="dist/index.js"></script></body></html>');
165
+ }
166
+ /** The tab page at a plugin's `tab.path`: `dist/index.html`, the plugin's `static/index.html`, or an IIFE shell. */
167
+ tabPage(path) {
168
+ const listed = this.list().find((p) => p.tabPath === path);
169
+ const plugin = listed && this.enabled(listed.name) ? listed : null;
170
+ if (!plugin)
171
+ return null;
172
+ const own = this.builtAsset(plugin, 'dist/index.html');
173
+ // The tab page resolves relative URLs like the panel document beside it, at any mount depth.
174
+ if (own)
175
+ return html(withBase(own.body, `${baseHrefFor(path)}dashboard-plugins/${plugin.name}/`));
176
+ const page = readContainedFile(join(plugin.root, '..'), 'static/index.html');
177
+ if (page)
178
+ return html(page.body);
179
+ if (!this.builtAsset(plugin, 'dist/index.js'))
180
+ return null;
181
+ const base = `/dashboard-plugins/${plugin.name}/`;
182
+ const css = text(plugin.manifest.css, 300);
183
+ const cssTag = css ? `<link rel="stylesheet" href="${escapeHtml(base + css)}">` : '';
184
+ return html(`<!doctype html>\n<html lang="en">\n<head>\n <meta charset="utf-8">\n <title>${escapeHtml(text(plugin.manifest.label, 80) || plugin.name)}</title>\n ${cssTag}\n</head>\n` +
185
+ `<body>\n <div id="pluginPageContainer"></div>\n <script src="${base}dist/index.js"></script>\n</body>\n</html>\n`);
186
+ }
187
+ }
188
+ //# sourceMappingURL=dashboard-plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-plugins.js","sourceRoot":"","sources":["../../src/tools/dashboard-plugins.ts"],"names":[],"mappings":"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"}
@@ -16,12 +16,22 @@ export interface ExtensionDeps {
16
16
  export declare const validId: (v: unknown) => v is string;
17
17
  export declare function fullyUnquote(path: string): string;
18
18
  export declare function isSafeRelativePath(rel: string): boolean;
19
+ /**
20
+ * Read `<root>/<rel>` for a static route: `rel` must be a clean relative path (no dot segments or dotfiles), and the
21
+ * read is an anchored, symlink-free open, so a link inside the root cannot expose its target. Python
22
+ * `(root / rel).resolve()`: a symlinked file is served when its target stays inside the root, never otherwise.
23
+ */
24
+ export declare function readContainedFile(root: string, rel: string): {
25
+ body: Buffer;
26
+ file: string;
27
+ } | null;
19
28
  /**
20
29
  * Python `_normalize_loopback_sidecar_origin` (`urlsplit`, not WHATWG): scheme + literal loopback host + optional
21
30
  * numeric port, nothing else — no path (not even `/`), no query, no fragment, no userinfo, no host aliases.
22
31
  */
23
32
  export declare function normalizeLoopbackOrigin(value: unknown): string | null;
24
33
  export declare function normalizeProxyPath(value: unknown): string | null;
34
+ export declare const text: (v: unknown, max?: number) => string;
25
35
  export declare class ExtensionService {
26
36
  private readonly deps;
27
37
  private registryCache;
@@ -52,6 +62,8 @@ export declare class ExtensionService {
52
62
  /** Python `get_extension_config()["enabled"]`: true whenever the extension root directory exists (a manifest is optional). */
53
63
  enabledSync(): boolean;
54
64
  status(): Promise<Dict>;
65
+ /** The status payload plus the manifest and sidecar records it was built from, so `manifests()` reads the same load. */
66
+ private snapshot;
55
67
  private loaded;
56
68
  private mutateState;
57
69
  setEnabled(idRaw: unknown, enabled: unknown): Promise<Dict>;
@@ -84,7 +96,7 @@ export declare class ExtensionService {
84
96
  contentType: string;
85
97
  html: boolean;
86
98
  } | null;
87
- /** Python `api/extension_manifests.build_manifests` (extension entries only; dashboard plugins were dropped). */
99
+ /** Python `api/extension_manifests.build_manifests` extension entries (the manifests route appends dashboard plugins). */
88
100
  manifests(): Promise<{
89
101
  protocol_version: 1;
90
102
  manifests: Dict[];
@@ -9,6 +9,7 @@ import { atomicWriteText } from '../fs/atomic.js';
9
9
  import { readZip } from '../workspace/unzip.js';
10
10
  import { isDict } from '../config/agent-config.js';
11
11
  import { str } from '../util.js';
12
+ import { SKIN_TOKEN_NAMES, SKIN_VALUE_RE } from '@maudecode/talaria-web-contracts';
12
13
  export class ExtensionError extends Error {
13
14
  status;
14
15
  constructor(message, status = 400) {
@@ -34,6 +35,9 @@ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
34
35
  const ALLOWED_ASSET_PREFIXES = ['/extensions/', '/static/'];
35
36
  const ALLOWED_DOTFILES = new Set(['.gitkeep', '.gitignore', '.gitattributes', '.env.example']);
36
37
  export const EXTENSION_MIME = { css: 'text/css', js: 'application/javascript', html: 'text/html', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', ico: 'image/x-icon', gif: 'image/gif', webp: 'image/webp', woff: 'font/woff', woff2: 'font/woff2', ttf: 'font/ttf', otf: 'font/otf', wasm: 'application/wasm' };
38
+ const SKIN_TOKENS = new Set(SKIN_TOKEN_NAMES);
39
+ /** Skin keys and TTS engine ids share the frontend contract's slug. */
40
+ const DECLARATION_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
37
41
  const TEXT_MIME = new Set(['text/css', 'application/javascript', 'text/html', 'image/svg+xml', 'text/plain']);
38
42
  export const validId = (v) => typeof v === 'string' && ID_RE.test(v.trim());
39
43
  const warn = (d, code, source) => { if (d && !d.warnings.some((w) => w.code === code && w.source === source))
@@ -83,6 +87,41 @@ function isSafeAssetUrl(value) {
83
87
  return isSafeRelativePath(decoded.slice(prefix.length));
84
88
  return false;
85
89
  }
90
+ /**
91
+ * Read `<root>/<rel>` for a static route: `rel` must be a clean relative path (no dot segments or dotfiles), and the
92
+ * read is an anchored, symlink-free open, so a link inside the root cannot expose its target. Python
93
+ * `(root / rel).resolve()`: a symlinked file is served when its target stays inside the root, never otherwise.
94
+ */
95
+ export function readContainedFile(root, rel) {
96
+ if (!isSafeRelativePath(rel))
97
+ return null;
98
+ const anchor = resolvePathLikePython(root);
99
+ let file = resolve(anchor, rel);
100
+ if (!file.startsWith(anchor + sep))
101
+ return null;
102
+ const real = resolvePathLikePython(file);
103
+ if (real !== file) {
104
+ if (!real.startsWith(anchor + sep))
105
+ return null;
106
+ file = real;
107
+ }
108
+ let fd;
109
+ try {
110
+ fd = openAnchoredFd(anchor, file, { wantDir: false });
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ try {
116
+ return fstatSync(fd).isFile() ? { body: readFileSync(fd), file } : null;
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ finally {
122
+ closeSync(fd);
123
+ }
124
+ }
86
125
  /**
87
126
  * Python `_normalize_loopback_sidecar_origin` (`urlsplit`, not WHATWG): scheme + literal loopback host + optional
88
127
  * numeric port, nothing else — no path (not even `/`), no query, no fragment, no userinfo, no host aliases.
@@ -144,7 +183,7 @@ export function normalizeProxyPath(value) {
144
183
  const segments = decoded.split('/').slice(1);
145
184
  return segments.length && segments.every((s) => s && s !== '.' && s !== '..') ? candidate : null;
146
185
  }
147
- const text = (v, max = 160) => (typeof v === 'string' ? v.replaceAll(/[\x00-\x1f\x7f]/g, '').trim().slice(0, max) : '');
186
+ export const text = (v, max = 160) => (typeof v === 'string' ? v.replaceAll(/[\x00-\x1f\x7f]/g, '').trim().slice(0, max) : '');
148
187
  const entryText = (e, k) => (typeof e[k] === 'string' ? e[k].trim() : '');
149
188
  const storageOwned = (e) => isDict(e.permissions) && isDict(e.permissions.storage) && e.permissions.storage.owned === true;
150
189
  function settingsSchema(entry) {
@@ -268,6 +307,28 @@ function sidecarFromEntry(entry, d) {
268
307
  }
269
308
  return { id: entryText(entry, 'id'), name: entryText(entry, 'name'), type: 'loopback', origin, health_path: healthPath, health_url: `${origin}${healthPath}`, proxy_auth: proxyAuth };
270
309
  }
310
+ /**
311
+ * A manifest `theme` reduced to the frontend skin contract: allowlisted tokens whose values pass the shared value check,
312
+ * keyed `<extension-id>:<key>` so it never collides with a built-in skin or another extension's skin.
313
+ */
314
+ function themeFromEntry(raw, id) {
315
+ if (!isDict(raw) || typeof raw.key !== 'string' || !DECLARATION_ID_RE.test(raw.key))
316
+ return null;
317
+ const name = text(raw.name, 40);
318
+ const tokens = isDict(raw.tokens) ? Object.fromEntries(Object.entries(raw.tokens).filter(([k, v]) => SKIN_TOKENS.has(k) && typeof v === 'string' && SKIN_VALUE_RE.test(v))) : {};
319
+ if (!name || !Object.keys(tokens).length)
320
+ return null;
321
+ const colors = Array.isArray(raw.colors) ? raw.colors.filter((c) => typeof c === 'string' && SKIN_VALUE_RE.test(c)).slice(0, 3) : null;
322
+ return { key: `${id}:${raw.key}`, name, ...(raw.scheme === 'light' || raw.scheme === 'dark' ? { scheme: raw.scheme } : {}), ...(colors ? { colors } : {}), tokens };
323
+ }
324
+ function ttsFromEntry(raw) {
325
+ if (!isDict(raw) || typeof raw.id !== 'string' || !DECLARATION_ID_RE.test(raw.id))
326
+ return null;
327
+ const label = text(raw.label, 60);
328
+ return label ? { id: raw.id, label } : null;
329
+ }
330
+ /** Entries the gallery loader read from `<root>/<id>/`, keyed by object so a manifest can never claim gallery provenance. */
331
+ const galleryFolders = new WeakMap();
271
332
  export class ExtensionService {
272
333
  deps;
273
334
  registryCache = null;
@@ -437,10 +498,11 @@ export class ExtensionService {
437
498
  for (const id of Object.keys(installed).sort()) {
438
499
  if (!validId(id))
439
500
  continue;
440
- const file = join(root, id, 'manifest.json');
501
+ // `manifest.json` wins; a gallery zip may ship only `extension.json` (docs/EXTENSIONS.md).
441
502
  let manifest;
442
503
  try {
443
- if (!existsSync(file) || !statSync(file).isFile()) {
504
+ const file = ['manifest.json', 'extension.json'].map((name) => join(root, id, name)).find((p) => existsSync(p) && statSync(p).isFile());
505
+ if (!file) {
444
506
  warn(d, 'gallery_manifest_missing', 'gallery');
445
507
  continue;
446
508
  }
@@ -457,17 +519,26 @@ export class ExtensionService {
457
519
  }
458
520
  const withBase = (entry) => {
459
521
  const copy = { ...entry };
522
+ // Paths already rooted at the id (the `extensions.json` shape, as in the shipped example) are not prefixed twice.
523
+ const rebase = (v) => {
524
+ if (typeof v !== 'string' || !v.trim() || /^[a-z]+:|^\/\/|^\//i.test(v.trim()))
525
+ return v;
526
+ return v.trim().startsWith(`${id}/`) ? v.trim() : `${id}/${v.trim()}`;
527
+ };
460
528
  for (const key of ['scripts', 'stylesheets'])
461
529
  if (Array.isArray(copy[key]))
462
- copy[key] = copy[key].map((v) => (typeof v === 'string' && v.trim() && !/^[a-z]+:|^\/\/|^\//i.test(v.trim()) ? `${id}/${v.trim()}` : v));
530
+ copy[key] = copy[key].map(rebase);
531
+ if (typeof copy.panel === 'string')
532
+ copy.panel = rebase(copy.panel.trim().replace(/^(\.\/)+/, ''));
533
+ galleryFolders.set(copy, id);
463
534
  return copy;
464
535
  };
465
536
  if (isDict(manifest)) {
466
537
  const top = { id };
467
- for (const key of ['name', 'enabled', 'scripts', 'stylesheets', 'sidecar', 'permissions', 'settings_schema'])
538
+ for (const key of ['name', 'enabled', 'version', 'description', 'panel', 'nav', 'capabilities', 'scripts', 'stylesheets', 'sidecar', 'theme', 'tts', 'permissions', 'settings_schema'])
468
539
  if (key in manifest)
469
540
  top[key] = manifest[key];
470
- if (['scripts', 'stylesheets', 'sidecar', 'permissions', 'settings_schema'].some((k) => k in top))
541
+ if (['panel', 'scripts', 'stylesheets', 'sidecar', 'theme', 'tts', 'permissions', 'settings_schema'].some((k) => k in top))
471
542
  entries.push(withBase(top));
472
543
  }
473
544
  for (const entry of manifestEntries(manifest)) {
@@ -650,9 +721,9 @@ export class ExtensionService {
650
721
  break;
651
722
  }
652
723
  }
653
- const { records } = await this.sidecarRecords(manifest, disabled, state, d);
724
+ const { records, byId } = await this.sidecarRecords(manifest, disabled, state, d);
654
725
  Object.assign(status, { loaded: true, status: str(status.status) || 'loaded', script_count: scripts.length, stylesheet_count: stylesheets.length, sidecar_count: records.length });
655
- return { scripts, stylesheets, sidecars: records };
726
+ return { scripts, stylesheets, sidecars: records, sidecarsById: byId };
656
727
  }
657
728
  /** Python `get_extension_status`. */
658
729
  /** Python `get_extension_config()["enabled"]` for the bootstrap feature flag: a valid root with a loadable manifest. */
@@ -661,6 +732,10 @@ export class ExtensionService {
661
732
  return this.root() !== null;
662
733
  }
663
734
  async status() {
735
+ return (await this.snapshot()).status;
736
+ }
737
+ /** The status payload plus the manifest and sidecar records it was built from, so `manifests()` reads the same load. */
738
+ async snapshot() {
664
739
  const d = { warnings: [] };
665
740
  const envDir = (this.deps.env.HERMES_WEBUI_EXTENSION_DIR ?? '').trim();
666
741
  const root = this.root();
@@ -670,20 +745,20 @@ export class ExtensionService {
670
745
  if (envDir && !root)
671
746
  warn(d, 'extension_dir_unavailable', 'extension_dir');
672
747
  if (!root) {
673
- return { enabled: false, extension_dir_configured: true, extension_dir_valid: false, script_urls: [], stylesheet_urls: [], sidecars: [], counts: { script_urls: 0, stylesheet_urls: 0, sidecars: 0, manifest_extensions: 0, user_disabled: 0 }, manifest: { configured: manifestConfigured, loaded: false, status: manifestConfigured ? 'extension_disabled' : 'not_configured', entry_count: 0, script_count: 0, stylesheet_count: 0, sidecar_count: 0 }, extensions: [], warnings: d.warnings };
748
+ return { manifest: null, assetBase: '', sidecarsById: new Map(), status: { enabled: false, extension_dir_configured: true, extension_dir_valid: false, script_urls: [], stylesheet_urls: [], sidecars: [], counts: { script_urls: 0, stylesheet_urls: 0, sidecars: 0, manifest_extensions: 0, user_disabled: 0 }, manifest: { configured: manifestConfigured, loaded: false, status: manifestConfigured ? 'extension_disabled' : 'not_configured', entry_count: 0, script_count: 0, stylesheet_count: 0, sidecar_count: 0 }, extensions: [], warnings: d.warnings } };
674
749
  }
675
750
  const [manifest, status] = this.loadManifest(root, d);
676
751
  const consentIds = new Set(Object.keys(state.sidecar_proxy_consents));
677
752
  const ext = manifest !== null ? this.extensionState(manifest, disabled, d, consentIds) : { extensions: [], knownIds: new Set(), manifestDisabled: new Set() };
678
- const urls = manifest !== null ? await this.manifestUrls(manifest, status, disabled, state, d) : { scripts: [], stylesheets: [], sidecars: [] };
753
+ const urls = manifest !== null ? await this.manifestUrls(manifest, status, disabled, state, d) : { scripts: [], stylesheets: [], sidecars: [], sidecarsById: new Map() };
679
754
  const scriptUrls = this.envUrls('HERMES_WEBUI_EXTENSION_SCRIPT_URLS', urls.scripts.length ? urls.scripts : null, d);
680
755
  const stylesheetUrls = this.envUrls('HERMES_WEBUI_EXTENSION_STYLESHEET_URLS', urls.stylesheets.length ? urls.stylesheets : null, d);
681
756
  const publicStatus = Object.fromEntries(Object.entries(status).filter(([k]) => !k.startsWith('_')));
682
- return {
683
- enabled: true, extension_dir_configured: true, extension_dir_valid: true, script_urls: scriptUrls, stylesheet_urls: stylesheetUrls, sidecars: urls.sidecars,
684
- counts: { script_urls: scriptUrls.length, stylesheet_urls: stylesheetUrls.length, sidecars: urls.sidecars.length, manifest_extensions: ext.extensions.length, user_disabled: [...disabled].filter((id) => ext.knownIds.has(id)).length },
685
- manifest: publicStatus, extensions: ext.extensions, gallery_installed: this.loadInstallManifest().installed, warnings: d.warnings,
686
- };
757
+ return { manifest, assetBase: str(status._asset_base), sidecarsById: urls.sidecarsById, status: {
758
+ enabled: true, extension_dir_configured: true, extension_dir_valid: true, script_urls: scriptUrls, stylesheet_urls: stylesheetUrls, sidecars: urls.sidecars,
759
+ counts: { script_urls: scriptUrls.length, stylesheet_urls: stylesheetUrls.length, sidecars: urls.sidecars.length, manifest_extensions: ext.extensions.length, user_disabled: [...disabled].filter((id) => ext.knownIds.has(id)).length },
760
+ manifest: publicStatus, extensions: ext.extensions, gallery_installed: this.loadInstallManifest().installed, warnings: d.warnings,
761
+ } };
687
762
  }
688
763
  loaded(root, d) {
689
764
  const state = this.loadState(d);
@@ -1022,44 +1097,26 @@ export class ExtensionService {
1022
1097
  /** Python `serve_extension_static`: bytes, MIME, sandbox CSP for HTML; null means 404. */
1023
1098
  staticFile(rel) {
1024
1099
  const root = this.root();
1025
- if (!root || !isSafeRelativePath(rel))
1026
- return null;
1027
- // Python `serve_extension_static`: an anchored, symlink-free open so a link inside the extension dir cannot expose its target.
1028
- const anchor = resolvePathLikePython(root);
1029
- let file = resolve(anchor, rel);
1030
- if (!file.startsWith(anchor + sep))
1031
- return null;
1032
- // Python `(root / rel).resolve()`: a symlinked asset is served when its target stays inside the root, never otherwise.
1033
- const real = resolvePathLikePython(file);
1034
- if (real !== file) {
1035
- if (!real.startsWith(anchor + sep))
1036
- return null;
1037
- file = real;
1038
- }
1039
- let fd;
1040
- try {
1041
- fd = openAnchoredFd(anchor, file, { wantDir: false });
1042
- }
1043
- catch {
1044
- return null;
1045
- }
1046
- try {
1047
- if (!fstatSync(fd).isFile())
1048
- return null;
1049
- const ext = file.slice(file.lastIndexOf('.') + 1).toLowerCase();
1050
- const ct = EXTENSION_MIME[ext] ?? 'text/plain';
1051
- return { body: readFileSync(fd), contentType: TEXT_MIME.has(ct) ? `${ct}; charset=utf-8` : ct, html: ct === 'text/html' };
1052
- }
1053
- catch {
1100
+ const found = root ? readContainedFile(root, rel) : null;
1101
+ if (!found)
1054
1102
  return null;
1055
- }
1056
- finally {
1057
- closeSync(fd);
1058
- }
1103
+ const ext = found.file.slice(found.file.lastIndexOf('.') + 1).toLowerCase();
1104
+ const ct = EXTENSION_MIME[ext] ?? 'text/plain';
1105
+ return { body: found.body, contentType: TEXT_MIME.has(ct) ? `${ct}; charset=utf-8` : ct, html: ct === 'text/html' };
1059
1106
  }
1060
- /** Python `api/extension_manifests.build_manifests` (extension entries only; dashboard plugins were dropped). */
1107
+ /** Python `api/extension_manifests.build_manifests` extension entries (the manifests route appends dashboard plugins). */
1061
1108
  async manifests() {
1062
- const status = await this.status();
1109
+ const { status, manifest, assetBase, sidecarsById } = await this.snapshot();
1110
+ // Status rows carry only enable state; panel, nav, capabilities, assets, and metadata come from the first manifest entry per id.
1111
+ const rawById = new Map();
1112
+ for (const e of manifestEntries(manifest)) {
1113
+ const id = entryText(e, 'id');
1114
+ if (!rawById.has(id))
1115
+ rawById.set(id, e);
1116
+ }
1117
+ // Gallery rows are the entries the gallery loader read from their own install folder; `uninstall` accepts exactly the
1118
+ // ids in the install manifest, so a configured entry that shares an installed id never offers Uninstall.
1119
+ const installed = isDict(status.gallery_installed) ? status.gallery_installed : {};
1063
1120
  const manifests = [];
1064
1121
  const seen = new Set();
1065
1122
  const ID = /^[a-z][a-z0-9_-]{0,63}$/;
@@ -1068,27 +1125,42 @@ export class ExtensionService {
1068
1125
  if (!ID.test(id) || seen.has(id))
1069
1126
  continue;
1070
1127
  seen.add(id);
1071
- const rawManifest = isDict(entry.manifest) ? entry.manifest : entry;
1128
+ const rawManifest = rawById.get(str(entry.id)) ?? {};
1072
1129
  const warnings = [];
1073
1130
  let panel = null;
1074
1131
  const rawPanel = rawManifest.panel;
1075
1132
  if (rawPanel !== null && rawPanel !== undefined) {
1076
- if (typeof rawPanel !== 'string' || !/^[A-Za-z0-9._/-]{1,300}$/.test(rawPanel) || rawPanel.split('/').includes('..') || rawPanel.startsWith('/'))
1133
+ // Like scripts, a panel path is relative to the manifest's folder; gallery entries were rebased onto `<id>/`.
1134
+ const url = typeof rawPanel === 'string' ? `extensions/${assetBase ? `${assetBase}/` : ''}${rawPanel.replace(/^(\.\/)+/, '')}` : '';
1135
+ if (typeof rawPanel !== 'string' || !/^[A-Za-z0-9._/-]{1,300}$/.test(rawPanel) || !isSafeRelativePath(url) || url.length > 400)
1077
1136
  warnings.push('panel_path_rejected');
1078
1137
  else if (!rawPanel.toLowerCase().endsWith('.html'))
1079
1138
  warnings.push('panel_not_html');
1080
1139
  else
1081
- panel = `extensions/${id}/${rawPanel.replace(/^[./]+/, '')}`;
1140
+ panel = url;
1082
1141
  }
1083
- const scripts = Array.isArray(entry.scripts) ? entry.scripts : Array.isArray(rawManifest.scripts) ? rawManifest.scripts : [];
1084
- const stylesheets = Array.isArray(entry.stylesheets) ? entry.stylesheets : Array.isArray(rawManifest.stylesheets) ? rawManifest.stylesheets : [];
1085
- const legacy = Boolean((scripts.length || stylesheets.length) && !panel);
1142
+ const scripts = Array.isArray(rawManifest.scripts) ? rawManifest.scripts : [];
1143
+ const stylesheets = Array.isArray(rawManifest.stylesheets) ? rawManifest.stylesheets : [];
1144
+ const theme = themeFromEntry(rawManifest.theme, id);
1145
+ if (!theme && rawManifest.theme != null)
1146
+ warnings.push('theme_rejected');
1147
+ // A skin is the one declaration that works without a panel, so it keeps an entry with leftover assets runnable.
1148
+ const legacy = Boolean((scripts.length || stylesheets.length) && !panel && !theme);
1086
1149
  const rawNav = isDict(rawManifest.nav) ? rawManifest.nav : {};
1087
- const nav = panel ? { label: text(rawNav.label, 40) || text(entry.name ?? rawManifest.name, 40) || id, ...(text(rawNav.icon, 40) ? { icon: text(rawNav.icon, 40) } : {}) } : null;
1088
- const permissions = isDict(entry.permissions) ? Object.fromEntries(Object.entries(entry.permissions).map(([k, v]) => [k, Boolean(v)])) : {};
1089
- const enabledFlag = entry.effective_enabled ?? entry.enabled ?? true;
1150
+ const nav = panel ? { label: text(rawNav.label, 40) || text(entry.name, 40) || id, ...(text(rawNav.icon, 40) ? { icon: text(rawNav.icon, 40) } : {}) } : null;
1151
+ const permissions = isDict(rawManifest.permissions) ? Object.fromEntries(Object.entries(rawManifest.permissions).filter(([k]) => k.length <= 64).map(([k, v]) => [k, Boolean(v)])) : {};
1152
+ const tts = ttsFromEntry(rawManifest.tts);
1153
+ if (!tts && rawManifest.tts != null)
1154
+ warnings.push('tts_rejected');
1155
+ // The sidecar the proxy would serve: same record, availability, and saved consent `proxyTarget` enforces
1156
+ // (which refuses token-v1 sidecars in this release).
1157
+ const record = sidecarsById.get(str(entry.id));
1158
+ const declared = isDict(record?.sidecar) && record.sidecar.proxy_auth !== 'token-v1' ? record.sidecar : null;
1159
+ const proxy = isDict(record?.proxy) ? record.proxy : null;
1160
+ const healthPath = declared ? str(declared.health_path) : '';
1161
+ const sidecar = declared && proxy?.available === true ? { origin: str(declared.origin), ...(healthPath.length <= 200 ? { health_path: healthPath } : {}), consented: proxy.consented === true } : null;
1090
1162
  const caps = (Array.isArray(rawManifest.capabilities) ? rawManifest.capabilities : []).filter((c) => typeof c === 'string' && ['settings', 'storage', 'sidecar', 'lifecycle', 'theme', 'tts', 'navigate', 'toast', 'session'].includes(c));
1091
- manifests.push({ id, name: text(entry.name ?? rawManifest.name, 80) || id, version: text(entry.version ?? rawManifest.version, 40), description: text(entry.description ?? rawManifest.description, 300), source: entry.gallery_installed || entry.source === 'gallery' ? 'gallery' : 'manifest', enabled: Boolean(enabledFlag) && !legacy, panel, nav, capabilities: [...new Set(caps)], permissions, settings_schema: Array.isArray(entry.settings_schema) ? entry.settings_schema : [], theme: null, tts: null, sidecar: null, legacy_injection: legacy, warnings });
1163
+ manifests.push({ id, name: text(entry.name, 80) || id, version: text(rawManifest.version, 40), description: text(rawManifest.description, 300), source: galleryFolders.get(rawManifest) === id && Object.hasOwn(installed, id) ? 'gallery' : 'manifest', enabled: entry.effective_enabled === true && !legacy, can_toggle: entry.can_toggle !== false && !legacy, panel, nav, capabilities: [...new Set(caps)], permissions, settings_schema: Array.isArray(entry.settings_schema) ? entry.settings_schema : [], theme, tts, sidecar, legacy_injection: legacy, warnings });
1092
1164
  }
1093
1165
  return { protocol_version: 1, manifests };
1094
1166
  }