@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.
- package/_release.json +8 -8
- package/dist/api/auth-raw.d.ts +1 -1
- package/dist/api/auth-raw.js +4 -4
- package/dist/api/auth-raw.js.map +1 -1
- package/dist/api/automation-raw.d.ts +6 -0
- package/dist/api/automation-raw.js +53 -6
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +26 -1
- package/dist/api/automation-router.js +16 -2
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +649 -15
- package/dist/api/chat-router.js +52 -50
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +35 -1
- package/dist/api/git-router.js +18 -19
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +8 -0
- package/dist/api/raw-routes.js +247 -36
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +10 -0
- package/dist/api/router.js +10 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +1492 -115
- package/dist/api/sessions-router.js +134 -8
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +31 -0
- package/dist/api/settings-router.js +8 -3
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +5 -0
- package/dist/api/sse-routes.js +50 -3
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -23
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +356 -15
- package/dist/api/tools-router.js +148 -19
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +35 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +5 -2
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +5 -2
- package/dist/auth/oidc.js +8 -7
- package/dist/auth/oidc.js.map +1 -1
- package/dist/bin/talaria-web.js +1 -1
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/dotenv.js +1 -0
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.js +3 -2
- package/dist/config/agent-config.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/http/context.d.ts +17 -3
- package/dist/http/context.js +31 -4
- package/dist/http/context.js.map +1 -1
- package/dist/http/csp.d.ts +2 -1
- package/dist/http/csp.js +3 -2
- package/dist/http/csp.js.map +1 -1
- package/dist/profiles/profiles.d.ts +10 -0
- package/dist/profiles/profiles.js +40 -2
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +6 -2
- package/dist/providers/catalog.js +30 -11
- package/dist/providers/catalog.js.map +1 -1
- package/dist/raster-image.d.ts +7 -0
- package/dist/raster-image.js +267 -0
- package/dist/raster-image.js.map +1 -0
- package/dist/redact.js +327 -40
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +89 -23
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +2 -9
- package/dist/sessions/background-tasks.js +2 -12
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +15 -4
- package/dist/sessions/cli-sessions.js +120 -29
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +48 -8
- package/dist/sessions/completions.js +222 -26
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/export.d.ts +5 -0
- package/dist/sessions/export.js +30 -0
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-watcher.d.ts +7 -3
- package/dist/sessions/gateway-watcher.js +31 -19
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/handoff.d.ts +32 -0
- package/dist/sessions/handoff.js +114 -0
- package/dist/sessions/handoff.js.map +1 -0
- package/dist/sessions/list.d.ts +7 -1
- package/dist/sessions/list.js +13 -5
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +2 -4
- package/dist/sessions/merge.js +8 -15
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/recovery.d.ts +22 -0
- package/dist/sessions/recovery.js +506 -0
- package/dist/sessions/recovery.js.map +1 -0
- package/dist/sessions/relay.d.ts +21 -2
- package/dist/sessions/relay.js +156 -11
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +108 -6
- package/dist/sessions/service.js +403 -29
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +6 -0
- package/dist/sessions/session.js +40 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/state-db-worker.d.ts +1 -0
- package/dist/sessions/state-db-worker.js +33 -0
- package/dist/sessions/state-db-worker.js.map +1 -0
- package/dist/sessions/state-db.d.ts +61 -0
- package/dist/sessions/state-db.js +291 -0
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.d.ts +7 -0
- package/dist/sessions/store.js +31 -22
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/titles.d.ts +2 -0
- package/dist/sessions/titles.js +4 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +3 -3
- package/dist/sessions/tool-display.js +11 -37
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +1 -1
- package/dist/sessions/turn-context.js +11 -7
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +37 -2
- package/dist/sessions/turn.js +182 -49
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/wakeup-pause.d.ts +59 -0
- package/dist/sessions/wakeup-pause.js +181 -0
- package/dist/sessions/wakeup-pause.js.map +1 -0
- package/dist/settings.js +5 -7
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/startup.d.ts +13 -5
- package/dist/startup.js +48 -0
- package/dist/startup.js.map +1 -1
- package/dist/test/ts-worker-hooks.d.ts +1 -0
- package/dist/test/ts-worker-hooks.js +24 -0
- package/dist/test/ts-worker-hooks.js.map +1 -0
- package/dist/text/diff.d.ts +9 -14
- package/dist/text/diff.js +74 -108
- package/dist/text/diff.js.map +1 -1
- package/dist/tools/crons.js +45 -8
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +53 -0
- package/dist/tools/dashboard-plugins.js +188 -0
- package/dist/tools/dashboard-plugins.js.map +1 -0
- package/dist/tools/extensions.d.ts +13 -1
- package/dist/tools/extensions.js +132 -60
- package/dist/tools/extensions.js.map +1 -1
- package/dist/tools/health.d.ts +32 -0
- package/dist/tools/health.js +111 -19
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/mcp.d.ts +13 -0
- package/dist/tools/mcp.js +88 -0
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/project-os.d.ts +11 -0
- package/dist/tools/project-os.js +267 -0
- package/dist/tools/project-os.js.map +1 -0
- package/dist/tools/skills.d.ts +5 -1
- package/dist/tools/skills.js +5 -3
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/updates.d.ts +27 -9
- package/dist/tools/updates.js +163 -51
- package/dist/tools/updates.js.map +1 -1
- package/dist/tools/wiki.d.ts +68 -0
- package/dist/tools/wiki.js +413 -0
- package/dist/tools/wiki.js.map +1 -0
- package/dist/workspace/escape.d.ts +61 -0
- package/dist/workspace/escape.js +149 -0
- package/dist/workspace/escape.js.map +1 -0
- package/dist/workspace/extract.d.ts +22 -0
- package/dist/workspace/extract.js +308 -0
- package/dist/workspace/extract.js.map +1 -0
- package/dist/workspace/fs.d.ts +12 -0
- package/dist/workspace/fs.js +92 -23
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/git.d.ts +18 -0
- package/dist/workspace/git.js +38 -25
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media.d.ts +4 -4
- package/dist/workspace/media.js +16 -45
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/rollback.d.ts +34 -20
- package/dist/workspace/rollback.js +171 -265
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/unzip.js +4 -1
- package/dist/workspace/unzip.js.map +1 -1
- package/dist/workspace/upload.d.ts +7 -4
- package/dist/workspace/upload.js +5 -2
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -1
- package/dist/workspace/worktrees.js +14 -15
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/scripts/replay_sidecar.py +24 -0
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +13 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +39 -5
- package/sidecar/talaria_sidecar/methods/chat.py +9 -5
- package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
- package/sidecar/talaria_sidecar/methods/commands.py +81 -0
- package/sidecar/talaria_sidecar/methods/cron.py +29 -2
- package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
- package/sidecar/talaria_sidecar/methods/kanban.py +28 -7
- package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
- package/sidecar/talaria_sidecar/methods/process.py +58 -4
- package/sidecar/talaria_sidecar/methods/profiles.py +77 -7
- package/sidecar/talaria_sidecar/methods/providers.py +27 -8
- package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
- package/sidecar/talaria_sidecar/runtime.py +17 -1
- package/static/dist/FILES.txt +127 -126
- package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
- package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
- package/static/dist/assets/Dialog-g355qxhp.js +1 -0
- package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
- package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
- package/static/dist/assets/{Markdown-mdwlvj3w.js → Markdown-j4kut4j2.js} +114 -114
- package/static/dist/assets/{RightPanel-ftvrjlcp.js → RightPanel-ohlsdzfl.js} +1 -1
- package/static/dist/assets/Select-kddq75qc.js +1 -0
- package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
- package/static/dist/assets/{SettingsLayout-jonjefmj.js → SettingsLayout-o9bv4mgb.js} +1 -1
- package/static/dist/assets/{States-nmejrwrm.js → States-eq6fe29m.js} +1 -1
- package/static/dist/assets/Toaster-pc247u9f.js +1 -0
- package/static/dist/assets/{_app-gdidzd94.js → _app-f8n1i0m1.js} +1 -1
- package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-gfbzpoou.js} +1 -1
- package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
- package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
- package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
- package/static/dist/assets/_app.logs-i69qds98.js +1 -0
- package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
- package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-fn1bhde6.js} +1 -1
- package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
- package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
- package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
- package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
- package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
- package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
- package/static/dist/assets/{back-bemaer09.js → back-h7aun7ew.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
- package/static/dist/assets/{cancel-drd0g6d9.js → cancel-fhi0vcnu.js} +1 -1
- package/static/dist/assets/channel-b2zo73z1.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-dnau702j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
- package/static/dist/assets/{copied-ima3pysb.js → copied-hcplv46p.js} +1 -1
- package/static/dist/assets/{copy-fw5b1pc6.js → copy-magtofyp.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
- package/static/dist/assets/endpoints-klbxod4t.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
- package/static/dist/assets/host-i828xuo7.js +1 -0
- package/static/dist/assets/index-eh8zilnv.css +2 -0
- package/static/dist/assets/{index-cxq58xb1.js → index-r648f4o1.js} +5 -5
- package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-af0lvgjx.js → lazyRouteComponent-e1y4t2i9.js} +1 -1
- package/static/dist/assets/{line-osxq6fq7.js → line-meizqsvn.js} +1 -1
- package/static/dist/assets/{link-jyxicdhg.js → link-oot2yn73.js} +1 -1
- package/static/dist/assets/login-gu28l9ou.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-ntne45i9.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
- package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
- package/static/dist/assets/{onboarding_model_label-dp7xffoh.js → onboarding_model_label-ftpjdwqk.js} +1 -1
- package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
- package/static/dist/assets/{refresh-b4uams1i.js → refresh-abtu93ke.js} +1 -1
- package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-hlwb02qv.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
- package/static/dist/assets/{saved-kg52csy7.js → saved-girnabkf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-ej0iapxe.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
- package/static/dist/assets/share._token-kl0r7ido.js +2 -0
- package/static/dist/assets/{sliders-horizontal-fj18it47.js → sliders-horizontal-n1zm34qq.js} +1 -1
- package/static/dist/assets/sse-iwku0sjo.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
- package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
- package/static/dist/assets/{tts-lxdxnmjd.js → units-kgxzxfhi.js} +1 -1
- package/static/dist/assets/{untitled-ovpfthg4.js → untitled-cikfjntn.js} +1 -1
- package/static/dist/assets/{useForm-bcblv1rx.js → useForm-kqoqg0k9.js} +1 -1
- package/static/dist/assets/useLocale-o59tbiea.js +1 -0
- package/static/dist/assets/{useParams-lhg566ew.js → useParams-b4dq6n4q.js} +1 -1
- package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-ipfmv3he.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
- package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
- package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
- package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
- package/static/dist/assets/Select-bilaypej.js +0 -1
- package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
- package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
- package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
- package/static/dist/assets/_app.insights-luywlarz.js +0 -1
- package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
- package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
- package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
- package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
- package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
- package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
- package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
- package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
- package/static/dist/assets/channel-cpdgf867.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
- package/static/dist/assets/endpoints-d33siokx.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
- package/static/dist/assets/index-hgtx08n5.css +0 -2
- package/static/dist/assets/login-ec21t39x.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
- package/static/dist/assets/onboarding-btnltvyz.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
- package/static/dist/assets/share._token-f97e94r0.js +0 -2
- package/static/dist/assets/sse-fwpqtzbg.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
- package/static/dist/assets/todoStore-eginf9h4.js +0 -1
- 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`
|
|
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[];
|
package/dist/tools/extensions.js
CHANGED
|
@@ -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
|
-
|
|
501
|
+
// `manifest.json` wins; a gallery zip may ship only `extension.json` (docs/EXTENSIONS.md).
|
|
441
502
|
let manifest;
|
|
442
503
|
try {
|
|
443
|
-
|
|
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(
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
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`
|
|
1107
|
+
/** Python `api/extension_manifests.build_manifests` extension entries (the manifests route appends dashboard plugins). */
|
|
1061
1108
|
async manifests() {
|
|
1062
|
-
const status = await this.
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1140
|
+
panel = url;
|
|
1082
1141
|
}
|
|
1083
|
-
const scripts = Array.isArray(
|
|
1084
|
-
const stylesheets = Array.isArray(
|
|
1085
|
-
const
|
|
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
|
|
1088
|
-
const permissions = isDict(
|
|
1089
|
-
const
|
|
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
|
|
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
|
}
|