@maudecode/talaria-web 1.21.0 → 1.22.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 +4 -4
- package/dist/api/automation-router.d.ts +14 -14
- package/dist/api/chat-router.d.ts +217 -68
- package/dist/api/groups-router.d.ts +764 -0
- package/dist/api/groups-router.js +79 -0
- package/dist/api/groups-router.js.map +1 -0
- package/dist/api/raw-routes.js +3 -1
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/sessions-router.d.ts +559 -160
- package/dist/api/sessions-router.js +9 -12
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +911 -1
- package/dist/api/settings-router.js +53 -2
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +1 -1
- package/dist/api/sse-routes.js +11 -3
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +27 -136
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +109 -24
- package/dist/api/tools-router.js +64 -7
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +5 -1
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +5 -1
- package/dist/auth/gate.js.map +1 -1
- package/dist/config/agent-config.d.ts +6 -1
- package/dist/config/agent-config.js +75 -3
- package/dist/config/agent-config.js.map +1 -1
- package/dist/feedback.d.ts +18 -0
- package/dist/feedback.js +145 -0
- package/dist/feedback.js.map +1 -0
- package/dist/fs/stat.d.ts +2 -0
- package/dist/fs/stat.js +4 -0
- package/dist/fs/stat.js.map +1 -1
- package/dist/http/context.d.ts +7 -0
- package/dist/http/context.js +0 -6
- package/dist/http/context.js.map +1 -1
- package/dist/http/pinned.js +19 -8
- package/dist/http/pinned.js.map +1 -1
- package/dist/providers/catalog.d.ts +9 -1
- package/dist/providers/catalog.js +86 -6
- package/dist/providers/catalog.js.map +1 -1
- package/dist/redact.js +9 -3
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +27 -4
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +3 -2
- package/dist/sessions/anchor.js +11 -10
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/cli-sessions.js +11 -6
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/compress.d.ts +10 -9
- package/dist/sessions/compress.js +31 -20
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +11 -0
- package/dist/sessions/gateway-sessions.js +79 -24
- package/dist/sessions/gateway-sessions.js.map +1 -1
- package/dist/sessions/gateway-watcher.js +8 -4
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/groups.d.ts +60 -0
- package/dist/sessions/groups.js +339 -0
- package/dist/sessions/groups.js.map +1 -0
- package/dist/sessions/list.d.ts +7 -2
- package/dist/sessions/list.js +13 -4
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +14 -5
- package/dist/sessions/merge.js +42 -13
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +1 -1
- package/dist/sessions/pending.js +15 -6
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +1 -14
- package/dist/sessions/prefix-index.js +1 -50
- package/dist/sessions/prefix-index.js.map +1 -1
- package/dist/sessions/reasoning-display.d.ts +2 -0
- package/dist/sessions/reasoning-display.js +31 -0
- package/dist/sessions/reasoning-display.js.map +1 -0
- package/dist/sessions/relay.d.ts +4 -1
- package/dist/sessions/relay.js +44 -7
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +7 -2
- package/dist/sessions/service.js +104 -64
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +6 -0
- package/dist/sessions/session.js +13 -0
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.d.ts +1 -1
- package/dist/sessions/shares.js +13 -4
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/state-db.d.ts +2 -1
- package/dist/sessions/state-db.js +7 -3
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/storage/json-documents.js +2 -0
- package/dist/sessions/storage/json-documents.js.map +1 -1
- package/dist/sessions/store.d.ts +1 -0
- package/dist/sessions/store.js +10 -3
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +2 -0
- package/dist/sessions/streams.js +12 -0
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/todo.js +10 -1
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/turn.js +33 -9
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.js +2 -1
- package/dist/settings.js.map +1 -1
- package/dist/spa.js +1 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/group-fixture.d.ts +66 -0
- package/dist/test/group-fixture.js +89 -0
- package/dist/test/group-fixture.js.map +1 -0
- package/dist/test/harness.d.ts +3 -0
- package/dist/test/harness.js +3 -0
- package/dist/test/harness.js.map +1 -1
- package/dist/tools/connectors.d.ts +9 -1
- package/dist/tools/connectors.js +48 -0
- package/dist/tools/connectors.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +1 -1
- package/dist/tools/skills.d.ts +6 -0
- package/dist/tools/skills.js +17 -0
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/updates.d.ts +16 -9
- package/dist/tools/updates.js +63 -25
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +2 -0
- package/dist/workspace/fs.js +63 -1
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/upload.d.ts +1 -1
- package/dist/workspace/upload.js +52 -37
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +5 -1
- package/dist/workspace/workspaces.js +32 -6
- package/dist/workspace/workspaces.js.map +1 -1
- package/package.json +2 -2
- package/sidecar/agent_importer.json +8 -0
- package/sidecar/scripts/agent_importer_contract.py +19 -0
- package/sidecar/scripts/test.sh +3 -1
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +12 -5
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/agent_imports.py +346 -0
- package/sidecar/talaria_sidecar/methods/chat.py +141 -58
- package/sidecar/talaria_sidecar/methods/commands.py +6 -4
- package/sidecar/talaria_sidecar/methods/config.py +8 -3
- package/sidecar/talaria_sidecar/methods/connectors.py +138 -4
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +21 -0
- package/sidecar/talaria_sidecar/methods/delegation.py +126 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +5 -0
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +35 -6
- package/sidecar/talaria_sidecar/methods/groups.py +120 -0
- package/sidecar/talaria_sidecar/methods/memory.py +27 -3
- package/sidecar/talaria_sidecar/methods/nous_account.py +130 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +25 -4
- package/sidecar/talaria_sidecar/methods/outbound_drafts.py +1 -19
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +73 -2
- package/sidecar/talaria_sidecar/methods/providers.py +51 -1
- package/sidecar/talaria_sidecar/methods/skill_revisions.py +177 -0
- package/sidecar/talaria_sidecar/methods/skills.py +10 -0
- package/sidecar/talaria_sidecar/methods/tts.py +67 -0
- package/sidecar/talaria_sidecar/rpc.py +11 -2
- package/static/dist/FILES.txt +155 -143
- package/static/dist/THIRD_PARTY_LICENSES.md +1 -1
- package/static/dist/assets/AppShell-hvysbd9x.js +8 -0
- package/static/dist/assets/{Button-nuaikybd.js → Button-dlsibkzq.js} +1 -1
- package/static/dist/assets/ChatPage-jikwrwfd.js +2 -0
- package/static/dist/assets/Composer-mnke8ss2.js +5 -0
- package/static/dist/assets/ConnectTargets-ejplzo4q.js +1 -0
- package/static/dist/assets/Dialog-rksfyyq5.js +1 -0
- package/static/dist/assets/{ExtensionRoute-l1yjrso4.js → ExtensionRoute-igk5h0rj.js} +1 -1
- package/static/dist/assets/HubRoute-eooassx4.js +2 -0
- package/static/dist/assets/Markdown-m5pzgvtx.js +2 -0
- package/static/dist/assets/{MarkdownRenderer-ccgqrza3.js → MarkdownRenderer-74ysuomr.js} +6 -6
- package/static/dist/assets/OAuthSignIn-i4flsuq2.js +1 -0
- package/static/dist/assets/QueueCard-msz9wd1m.js +1 -0
- package/static/dist/assets/ResourceSwitch-b70zg4xd.js +1 -0
- package/static/dist/assets/RightPanel-f5v61m8j.js +1 -0
- package/static/dist/assets/Select-f3l897pt.js +1 -0
- package/static/dist/assets/SessionListPanel-brnzacw9.js +1 -0
- package/static/dist/assets/SettingsLayout-f4akmrt2.js +1 -0
- package/static/dist/assets/States-msohx3c0.js +1 -0
- package/static/dist/assets/TerminalPanel-bnu5xawo.js +16 -0
- package/static/dist/assets/Toaster-d6col3ch.js +1 -0
- package/static/dist/assets/_app-lrjgufcy.js +1 -0
- package/static/dist/assets/{_app.ext._extensionId-n797jp72.js → _app.ext._extensionId-k3xitxr1.js} +1 -1
- package/static/dist/assets/_app.groups-bt4wdwnd.js +1 -0
- package/static/dist/assets/{_app.index-ne43mdw2.js → _app.index-n3otiki0.js} +1 -1
- package/static/dist/assets/{_app.insights-fqonovb4.js → _app.insights-b0mzlh6m.js} +1 -1
- package/static/dist/assets/{_app.kanban-kysliv19.js → _app.kanban-d923g7j5.js} +1 -1
- package/static/dist/assets/{_app.logs-febut82r.js → _app.logs-donegue1.js} +1 -1
- package/static/dist/assets/{_app.memory-ha86oj1c.js → _app.memory-hdnled08.js} +1 -1
- package/static/dist/assets/{_app.profiles-cnu3gxxq.js → _app.profiles-fumot3jk.js} +1 -1
- package/static/dist/assets/{_app.session._sessionId-ifzjpcgn.js → _app.session._sessionId-crmdl2ux.js} +1 -1
- package/static/dist/assets/{_app.settings-nmakdiaf.js → _app.settings-jui8iesz.js} +1 -1
- package/static/dist/assets/_app.settings._section-do84hsqw.js +3 -0
- package/static/dist/assets/{_app.skills-f3wad8yo.js → _app.skills-heqcn21m.js} +1 -1
- package/static/dist/assets/_app.tasks-jiudtutt.js +1 -0
- package/static/dist/assets/{_app.todos-bml1m3r1.js → _app.todos-bl6kk2zq.js} +1 -1
- package/static/dist/assets/{_app.workspaces-f3d066lx.js → _app.workspaces-frvh3o7w.js} +1 -1
- package/static/dist/assets/_delete-qnxuo87s.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-i53jc4yp.js → abnfDiagram-VCTEODGH-dppg3yfx.js} +1 -1
- package/static/dist/assets/{architecture-7GRP2DOG-e3bv5m6f.js → architecture-7GRP2DOG-gbajejh3.js} +1 -1
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-pakekxu2.js → architectureDiagram-5GKGNRK7-bzrxyais.js} +1 -1
- package/static/dist/assets/archive-yhdyz231.js +1 -0
- package/static/dist/assets/autoReadClaim-nlyv8tu9.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bc6bo1n6.js → blockDiagram-I7D4REHJ-gmk9koab.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-btvwk2qx.js → c4Diagram-7LVT6UL2-bfqiqqcz.js} +1 -1
- package/static/dist/assets/channel-grt3rob4.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-dlwab11c.js → chunk-4HAMMTFA-i9s6h4wv.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-dp5gh7i8.js → chunk-75Z2AOVW-gnt6pnso.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bi06zl1c.js → chunk-DU6HZSFF-gq439wot.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-kvv1rnlh.js → chunk-F27PBJKO-wvx90pbt.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ji0rx9by.js → chunk-GMAD6QVW-mc2gu30k.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-nmz0j82p.js → chunk-GVQU2GXP-f8fxa2ym.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-kmi1qx88.js → chunk-IMKFNOWR-lnsl31hj.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-xhb7mebj.js → chunk-L3NEJ4N5-kbfdidwu.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-5a9sgscq.js → chunk-OSK3NFVY-dvxsduj6.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-dfezp4vu.js → chunk-P2QGCYS3-ivo3i1wf.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-gd2vq3c0.js → chunk-POPQ4Y6H-i0gfu0hx.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-wvw2nksf.js → chunk-PWAF6VOD-jb0fdyb0.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-f58q97px.js → chunk-SHT3W25Y-b7ou2xsp.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-abgwpyz9.js → chunk-SVP7TREG-e2v7me5v.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-h4sbs8ds.js → chunk-TICWLB2K-e5zlai62.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-nnsp49jr.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-nnsp49jr.js +1 -0
- package/static/dist/assets/completionAudio-c5at041z.js +1 -0
- package/static/dist/assets/{copy-m9h56qa8.js → copy-jj3oiw13.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-g081vffs.js → cynefin-OW5HDTMX-n5n1ck70.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-odxvjesk.js → cynefinDiagram-5FMLGOSQ-b33npu4f.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cpdwzjs4.js → dagre-GXQ25YYZ-miwvae07.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-clv5mxvu.js → diagram-S7CK7UJ4-j8fphsp4.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-m2ia1mbf.js → diagram-UQ7AKVKN-5cixo166.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-ea6zv636.js → diagram-VSXAHHWV-ii3bf92m.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-oqlef685.js → diagram-VX7I27RA-b3cfgx81.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-kjubcbwu.js → diagram-Z3DM3KII-bey9lqr8.js} +1 -1
- package/static/dist/assets/{dist-carq1azj.js → dist-cb8um5dh.js} +4 -4
- package/static/dist/assets/{dist-crc46di9.js → dist-kpg5ccm9.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-fg0zri3e.js → ebnfDiagram-PWID7BFC-2m4wrnqc.js} +1 -1
- package/static/dist/assets/{erDiagram-RLTQ6QDP-eb7pmk3x.js → erDiagram-RLTQ6QDP-kak5te0n.js} +1 -1
- package/static/dist/assets/{eventmodeling-NTZA5JFV-gvi1fxhn.js → eventmodeling-NTZA5JFV-ojcc486t.js} +1 -1
- package/static/dist/assets/flowDiagram-HODETNUW-kxr9sicu.js +1 -0
- package/static/dist/assets/folder-plus-fk6j7tlv.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-jmisowrc.js → ganttDiagram-EL5Y4UJY-k6nfmhrh.js} +1 -1
- package/static/dist/assets/getPseudoElementBounds-mwj3vqni.js +1 -0
- package/static/dist/assets/{gitGraph-4MIJSDKK-m7kt3u6e.js → gitGraph-4MIJSDKK-bxq1lbhy.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-nq7vfzh2.js → gitGraphDiagram-WWUBYQGX-ehvoankt.js} +1 -1
- package/static/dist/assets/group_discussions-fk5hry4l.js +1 -0
- package/static/dist/assets/{highlighted-body-KPVGNVTW-gcu017ay.js → highlighted-body-KPVGNVTW-nr4nfdq7.js} +1 -1
- package/static/dist/assets/{host-ipp4e84c.js → host-dhgqiozp.js} +1 -1
- package/static/dist/assets/index-hx7z4wdj.js +13 -0
- package/static/dist/assets/index-o8ebuc5x.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-idazwfqe.js → info-A6RAGUB7-dtsj0ybw.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-bb9jh8gl.js → infoDiagram-27XIBGKW-jena90ao.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-nqc132rv.js → ishikawaDiagram-5VMMS53U-kia6bn5u.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-fpvnhy44.js → journeyDiagram-3NMN7TZE-ipsa6icb.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-ndzqm6fu.js → kanban-definition-UXKFOSKX-moxx59kp.js} +1 -1
- package/static/dist/assets/katex-ic33v820.js +257 -0
- package/static/dist/assets/katex-qcv7xo0t.css +1 -0
- package/static/dist/assets/{lazyRouteComponent-javud235.js → lazyRouteComponent-i74ujrfk.js} +1 -1
- package/static/dist/assets/{line-g625e7cj.js → line-lgrkg9no.js} +1 -1
- package/static/dist/assets/link-2-fp49mwl2.js +1 -0
- package/static/dist/assets/{link-gfdaho08.js → link-es18mse1.js} +2 -2
- package/static/dist/assets/{locale-cs-n41hptse.js → locale-cs-brltdkcp.js} +1 -1
- package/static/dist/assets/{locale-de-k7ht47ew.js → locale-de-h6e9oq6v.js} +1 -1
- package/static/dist/assets/{locale-es-ih078lco.js → locale-es-oklmh80f.js} +1 -1
- package/static/dist/assets/{locale-fr-ojabnf41.js → locale-fr-b17ozoi2.js} +1 -1
- package/static/dist/assets/{locale-it-kb96jufy.js → locale-it-whe01eef.js} +1 -1
- package/static/dist/assets/{locale-ja-jpsp3xs1.js → locale-ja-g1s0rtpc.js} +1 -1
- package/static/dist/assets/{locale-pl-dbap4leu.js → locale-pl-d9p3lqcb.js} +1 -1
- package/static/dist/assets/{locale-pt-nci7w72x.js → locale-pt-l75on74f.js} +1 -1
- package/static/dist/assets/{locale-ru-soghe4cs.js → locale-ru-wl3mbsaw.js} +1 -1
- package/static/dist/assets/{locale-tr-ks00mu68.js → locale-tr-cz87itrq.js} +1 -1
- package/static/dist/assets/{locale-vi-ij7twfm6.js → locale-vi-elqj1y4k.js} +1 -1
- package/static/dist/assets/{locale-zh-Hant-feakwr7a.js → locale-zh-Hant-hf8gqm7d.js} +1 -1
- package/static/dist/assets/{locale-zh-ren0vjyv.js → locale-zh-cdckwb0u.js} +1 -1
- package/static/dist/assets/login-o6gfl4gy.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-n2kbzsao.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-plzkuzov.js → mermaid-parser.core-fflovwf2.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-ojrgs569.js → mindmap-definition-YA3MSWOX-ic8l9whi.js} +1 -1
- package/static/dist/assets/onboarding-bxm7gdrr.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-gyaf9s8h.js → packet-AYTQ26CC-ntci3088.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bf6xj8mq.js → pegDiagram-XKGWAZYB-e3btz04w.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-f5ymzelo.js → pie-WAS4IAKB-o2dc7l36.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-d0qjl3zi.js → pieDiagram-E7YTZNPT-gro55rrw.js} +1 -1
- package/static/dist/assets/presence-lq6m12ga.js +1 -0
- package/static/dist/assets/prompt_queue_cancelled-cebcuagu.js +1 -0
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-oqy1mras.js → quadrantDiagram-AXDQQJYC-fxulmd7d.js} +1 -1
- package/static/dist/assets/queryKeys-cnvn6pea.js +9 -0
- package/static/dist/assets/{radar-RG4KPBEZ-b6jcjvda.js → radar-RG4KPBEZ-9pcospra.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-e3a6lk5k.js → railroad-74A4TZTK-nzdga12j.js} +1 -1
- package/static/dist/assets/{railroad-abnf-HS5TGJTU-ff5hocrx.js → railroad-abnf-HS5TGJTU-ep3ytel0.js} +1 -1
- package/static/dist/assets/{railroad-ebnf-LZEXJU2U-exkhyw4n.js → railroad-ebnf-LZEXJU2U-iw7uc1wy.js} +1 -1
- package/static/dist/assets/{railroad-peg-WCYAUIDC-jefk9b7c.js → railroad-peg-WCYAUIDC-fo3b3gme.js} +1 -1
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-e4amks1i.js → railroadDiagram-O6MQD6OU-lxpxf8f0.js} +1 -1
- package/static/dist/assets/{refresh-cw-lfp4u1oh.js → refresh-cw-d14h79x5.js} +1 -1
- package/static/dist/assets/rename-ccd52yws.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-c77k1e2y.js → requirementDiagram-BXWQKSXE-lrozucd2.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-oa1cc52s.js → sankeyDiagram-P5KCCOFB-iusxy9mq.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-j20gqt22.js → sequenceDiagram-WJ2MYXX4-o987q8ti.js} +1 -1
- package/static/dist/assets/{share._token-kmfoflw6.js → share._token-bfw7f9m5.js} +2 -2
- package/static/dist/assets/sliders-horizontal-l65hoa3x.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-co2ghkdp.js → stateDiagram-D77RDMKH-hh23v7y5.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-jukr0d8h.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-m30cktqe.js → swimlanes-42K2YHIH-gj2t3rm8.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ik9m9j2m.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-liuep6fl.js → timeline-definition-24CTP7MA-84s2w2sr.js} +1 -1
- package/static/dist/assets/toast-gqqcvxiy.js +56 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-np5d2jv2.js → treeView-Q6P3EWNA-cmuj50l7.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-cmpltxh1.js → treemap-WGGIJYW6-dpjlg0xc.js} +1 -1
- package/static/dist/assets/tts-jw84jlqy.js +1 -0
- package/static/dist/assets/{useForm-f1udzsjf.js → useForm-lxzhiqao.js} +1 -1
- package/static/dist/assets/{useParams-ierevvts.js → useParams-dt6gkoq7.js} +1 -1
- package/static/dist/assets/{useSearch-i7nr9ypl.js → useSearch-o8t49csk.js} +1 -1
- package/static/dist/assets/useTranscript-gx9umcc2.js +1 -0
- package/static/dist/assets/{vennDiagram-4TSXK5OY-b2g42qrg.js → vennDiagram-4TSXK5OY-g9g5o8vy.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lb0eqbff.js → wardley-WFR3VGLG-ij35obrl.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ermwaqha.js → wardleyDiagram-VM6X3IG4-f9my7q7w.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dll0jldk.js → xychartDiagram-S5SC5T6Z-olqx1s1p.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-c5989995.js +0 -8
- package/static/dist/assets/ChatPage-bfg4khph.js +0 -6
- package/static/dist/assets/ConnectTargets-k5wo9yo8.js +0 -1
- package/static/dist/assets/Dialog-o8vo2aws.js +0 -1
- package/static/dist/assets/Field-j7o8cfps.js +0 -1
- package/static/dist/assets/HubRoute-efnwe4xz.js +0 -2
- package/static/dist/assets/Markdown-clqf5m22.js +0 -2
- package/static/dist/assets/OAuthSignIn-h7bjaqp6.js +0 -1
- package/static/dist/assets/ResourceSwitch-oai2zqaj.js +0 -1
- package/static/dist/assets/RightPanel-ho161a45.js +0 -1
- package/static/dist/assets/Select-d1g5a8bm.js +0 -1
- package/static/dist/assets/SessionListPanel-i7kaokhi.js +0 -1
- package/static/dist/assets/SettingsLayout-lnecm87w.js +0 -1
- package/static/dist/assets/States-easntmry.js +0 -1
- package/static/dist/assets/TerminalPanel-lxhwd4jl.js +0 -16
- package/static/dist/assets/Toaster-l4sciy8t.js +0 -1
- package/static/dist/assets/_app-mqexqbg8.js +0 -1
- package/static/dist/assets/_app.settings._section-djglnjmi.js +0 -3
- package/static/dist/assets/_app.tasks-l10py1c0.js +0 -1
- package/static/dist/assets/archive-ihqi7z2x.js +0 -1
- package/static/dist/assets/channel-jn4f8443.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-dzrw66se.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-dzrw66se.js +0 -1
- package/static/dist/assets/completionAudio-ce86msbu.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-abekab15.js +0 -1
- package/static/dist/assets/folder-plus-ksaons88.js +0 -1
- package/static/dist/assets/index-d6vbxlra.js +0 -13
- package/static/dist/assets/index-ey6qo0zx.css +0 -2
- package/static/dist/assets/katex-cbdd1ohw.css +0 -1
- package/static/dist/assets/katex-pbij2oo6.js +0 -257
- package/static/dist/assets/login-iv6yq4xf.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-cwj5cl6k.js +0 -1
- package/static/dist/assets/onboarding-ff4emacu.js +0 -1
- package/static/dist/assets/queryKeys-x8fjxhut.js +0 -9
- package/static/dist/assets/sliders-horizontal-g5jkzd9k.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-faw1opgh.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hfil433g.js +0 -8
- package/static/dist/assets/toast-zmb35u3r.js +0 -56
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Native delegation registry and route validation for the auxiliary settings owner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
|
|
7
|
+
from ..errors import InvalidParams, RpcError
|
|
8
|
+
from ..home import profile_home_param, scoped_home
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def registry(config: dict) -> dict:
|
|
12
|
+
section = config.get("delegation")
|
|
13
|
+
section = section if isinstance(section, dict) else {}
|
|
14
|
+
try:
|
|
15
|
+
from agent.delegation_backends import list_backends
|
|
16
|
+
from agent.delegation_registry import backend_status, configured_backend
|
|
17
|
+
except ImportError:
|
|
18
|
+
configured = section.get("backend") or "default"
|
|
19
|
+
return {"supported": False, "configured": configured, "effective": None, "available": False,
|
|
20
|
+
"blocked_reason": "Update Hermes Agent to configure delegation modes.", "fixed_model_active": configured == "default",
|
|
21
|
+
"backends": [{"name": configured, "label": configured, "description": "", "available": False, "reason": "Update Hermes Agent."}], "profiles": []}
|
|
22
|
+
from hermes_cli.plugins import discover_plugins
|
|
23
|
+
from hermes_cli.profiles import list_profile_names
|
|
24
|
+
|
|
25
|
+
discover_plugins()
|
|
26
|
+
configured = configured_backend(section)
|
|
27
|
+
provider, reason = backend_status(configured)
|
|
28
|
+
available = provider is not None and not reason
|
|
29
|
+
backends = [asdict(info) for info in list_backends()]
|
|
30
|
+
if not any(info["name"] == configured for info in backends):
|
|
31
|
+
backends.append({"name": configured, "label": configured, "description": "", "available": False, "reason": reason})
|
|
32
|
+
return {"supported": True, "configured": configured, "effective": configured if available else None,
|
|
33
|
+
"available": available, "blocked_reason": reason or None, "fixed_model_active": available and configured == "default",
|
|
34
|
+
"backends": backends, "profiles": list_profile_names()}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def clean_routes(backend: str, block: dict, current: dict) -> dict:
|
|
38
|
+
"""Use native canonical keys and parsers, preserving credentials by original route identity."""
|
|
39
|
+
from agent.delegation_provider import ROUTE_KEYS
|
|
40
|
+
from agent.fast_mode import STATIC_TIERS, parse_exact_service_tier
|
|
41
|
+
from hermes_constants import parse_reasoning_effort
|
|
42
|
+
from hermes_cli.profiles import list_profile_names
|
|
43
|
+
|
|
44
|
+
field = "options" if backend == "model_map" else "profiles"
|
|
45
|
+
entries = block.get(field)
|
|
46
|
+
if not isinstance(entries, list):
|
|
47
|
+
raise InvalidParams(f"{field} must be a list")
|
|
48
|
+
stored = {row.get("name"): row for row in current.get(field, []) if isinstance(row, dict)}
|
|
49
|
+
known = set(list_profile_names()) if backend == "profile_routes" else set()
|
|
50
|
+
rows, names = [], []
|
|
51
|
+
for entry in entries:
|
|
52
|
+
if not isinstance(entry, dict):
|
|
53
|
+
raise InvalidParams("Each route must be an object")
|
|
54
|
+
name = str(entry.get("name") or "").strip()
|
|
55
|
+
if not name or name in names:
|
|
56
|
+
raise InvalidParams("Route names must be nonempty and unique")
|
|
57
|
+
names.append(name)
|
|
58
|
+
if backend == "profile_routes":
|
|
59
|
+
if name not in known:
|
|
60
|
+
raise InvalidParams(f"Profile {name!r} does not exist")
|
|
61
|
+
rows.append({"name": name, **({"description": entry["description"].strip()} if entry.get("description", "").strip() else {})})
|
|
62
|
+
continue
|
|
63
|
+
source = stored.get(entry.get("original_name") or name, {})
|
|
64
|
+
# Hidden canonical values survive both edits and renames.
|
|
65
|
+
row = {key: value for key, value in source.items() if key in ROUTE_KEYS}
|
|
66
|
+
row.update({key: value for key, value in entry.items() if key in ROUTE_KEYS or key in {"name", "description"}})
|
|
67
|
+
row["name"] = name
|
|
68
|
+
for key in ("description", "model", "provider", "base_url", "api_mode"):
|
|
69
|
+
if key in row:
|
|
70
|
+
row[key] = str(row[key] or "").strip()
|
|
71
|
+
if not row.get("description") or not row.get("model") or not (row.get("provider") or row.get("base_url")):
|
|
72
|
+
raise InvalidParams(f"Route {name!r} needs a description, model and provider or endpoint")
|
|
73
|
+
for key in ("reasoning_effort", "service_tier"):
|
|
74
|
+
if key in entry and not entry[key]:
|
|
75
|
+
row.pop(key, None)
|
|
76
|
+
effort = row.get("reasoning_effort")
|
|
77
|
+
if effort and parse_reasoning_effort(effort) is None:
|
|
78
|
+
raise InvalidParams("Unknown reasoning effort")
|
|
79
|
+
tier = row.get("service_tier")
|
|
80
|
+
if tier:
|
|
81
|
+
try:
|
|
82
|
+
parsed = parse_exact_service_tier(tier)
|
|
83
|
+
except ValueError:
|
|
84
|
+
raise InvalidParams("Unknown service tier") from None
|
|
85
|
+
if parsed is None or parsed and parsed not in STATIC_TIERS:
|
|
86
|
+
raise InvalidParams("Unknown service tier")
|
|
87
|
+
if entry.get("api_key_clear"):
|
|
88
|
+
row.pop("api_key", None)
|
|
89
|
+
elif not entry.get("api_key") and source.get("api_key"):
|
|
90
|
+
row["api_key"] = source["api_key"]
|
|
91
|
+
if "fallback_providers" in row and not isinstance(row["fallback_providers"], list):
|
|
92
|
+
raise InvalidParams("fallback_providers must be a list")
|
|
93
|
+
rows.append(row)
|
|
94
|
+
default = (block.get("default") or "").strip()
|
|
95
|
+
if default and default not in names:
|
|
96
|
+
raise InvalidParams("The default route must be in the list")
|
|
97
|
+
return {field: rows, **({"default": default} if default else {})}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def register(methods) -> None:
|
|
101
|
+
@methods.method("aux.delegation")
|
|
102
|
+
def describe(ctx, params):
|
|
103
|
+
with scoped_home(profile_home_param(params)):
|
|
104
|
+
return registry(params.get("config") or {})
|
|
105
|
+
|
|
106
|
+
@methods.method("aux.delegation_routes")
|
|
107
|
+
def validate(ctx, params):
|
|
108
|
+
with scoped_home(profile_home_param(params)):
|
|
109
|
+
return {"block": clean_routes(params["backend"], params["block"], params["current"])}
|
|
110
|
+
|
|
111
|
+
@methods.method("aux.delegation_backend")
|
|
112
|
+
def select(ctx, params):
|
|
113
|
+
from hermes_cli.config import get_config_path
|
|
114
|
+
from agent.delegation_backends import set_global_backend
|
|
115
|
+
from .config import config_path_param
|
|
116
|
+
|
|
117
|
+
with scoped_home(profile_home_param(params)):
|
|
118
|
+
if get_config_path().resolve() != config_path_param(params).resolve():
|
|
119
|
+
raise RpcError("Native delegation config path differs from the configured file", condition="config_write_refused")
|
|
120
|
+
try:
|
|
121
|
+
set_global_backend(params["backend"])
|
|
122
|
+
except PermissionError:
|
|
123
|
+
raise RpcError("config.yaml is package-managed. Update the declarative configuration instead.", condition="config_write_refused") from None
|
|
124
|
+
except ValueError as exc:
|
|
125
|
+
raise InvalidParams(str(exc)) from None
|
|
126
|
+
return {"ok": True}
|
|
@@ -243,6 +243,11 @@ def _run(action: str, home: Path, cli_profile: str | None, ctx: CallContext, *,
|
|
|
243
243
|
|
|
244
244
|
|
|
245
245
|
def register(registry) -> None:
|
|
246
|
+
@registry.method("gateway.groups")
|
|
247
|
+
def groups_(ctx: CallContext, params: dict) -> dict:
|
|
248
|
+
from .groups import groups
|
|
249
|
+
return groups(ctx, params)
|
|
250
|
+
|
|
246
251
|
@registry.method("gateway.session")
|
|
247
252
|
def session_(ctx: CallContext, params: dict) -> dict:
|
|
248
253
|
from .gateway_sessions import session
|
|
@@ -70,6 +70,24 @@ def _control_refusal(home: Path, sid: str) -> str | None:
|
|
|
70
70
|
return None
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def _todo_state(value: object) -> dict | None:
|
|
74
|
+
if not isinstance(value, dict) or not isinstance(value.get("todos"), list):
|
|
75
|
+
return None
|
|
76
|
+
fields = {"id": (str, int, float), "text": (str,), "content": (str,), "title": (str,),
|
|
77
|
+
"status": (str,), "done": (bool,), "completed": (bool,)}
|
|
78
|
+
revision = value.get("revision", 0)
|
|
79
|
+
if type(revision) is not int or revision < 0:
|
|
80
|
+
return None
|
|
81
|
+
return {"todos": [{k: v for k, v in item.items() if k in fields and type(v) in fields[k]}
|
|
82
|
+
for item in value["todos"] if isinstance(item, dict)], "revision": revision}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _event(value: dict) -> dict:
|
|
86
|
+
if value.get("type") == "todo.updated":
|
|
87
|
+
return {**value, "payload": _todo_state(value.get("payload")) or {}}
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
73
91
|
def _snapshot(value: dict) -> dict:
|
|
74
92
|
# Raw conversation rows contain private principal/admission metadata; the client needs
|
|
75
93
|
# display rows, not those bindings or the inventory's system prompt and credential hint.
|
|
@@ -84,7 +102,8 @@ def _snapshot(value: dict) -> dict:
|
|
|
84
102
|
{"author": reaction["author"], "emoji": reaction["emoji"]} for reaction in reactions
|
|
85
103
|
if isinstance(reaction, dict) and reaction.get("author") in ("user", "agent")
|
|
86
104
|
and isinstance(reaction.get("emoji"), str) and reaction["emoji"] in _REACTION_EMOJIS] if isinstance(reactions, list) else []})
|
|
87
|
-
return {**value, "messages": rows, "info": {k: v for k, v in value["info"].items() if k in info_keys}
|
|
105
|
+
return {**value, "messages": rows, "info": {k: v for k, v in value["info"].items() if k in info_keys},
|
|
106
|
+
**({"todo_state": _todo_state(value["todo_state"])} if "todo_state" in value else {})}
|
|
88
107
|
|
|
89
108
|
|
|
90
109
|
def _refused(state: str, reason: str) -> dict:
|
|
@@ -92,7 +111,7 @@ def _refused(state: str, reason: str) -> dict:
|
|
|
92
111
|
|
|
93
112
|
|
|
94
113
|
@asynccontextmanager
|
|
95
|
-
async def _connect(home: Path, endpoint):
|
|
114
|
+
async def _connect(home: Path, endpoint, *, subject: str | None = None):
|
|
96
115
|
from hermes_constants import get_hermes_home
|
|
97
116
|
from hermes_cli.gateway_client import GatewayClientError, connect_gateway
|
|
98
117
|
|
|
@@ -100,7 +119,7 @@ async def _connect(home: Path, endpoint):
|
|
|
100
119
|
# override is mandatory so its bootstrap ticket cannot fall back to the launch profile.
|
|
101
120
|
if Path(get_hermes_home()).resolve() != home:
|
|
102
121
|
raise GatewayClientError("profile_mismatch")
|
|
103
|
-
async with connect_gateway(endpoint) as client:
|
|
122
|
+
async with connect_gateway(endpoint, **({"subject": subject} if subject is not None else {})) as client:
|
|
104
123
|
websocket = client.websocket
|
|
105
124
|
# GatewayClient queues server requests as events. Drain continuously, including
|
|
106
125
|
# during an RPC, so unsupported sudo/secure inputs never wait for a Web renderer.
|
|
@@ -211,7 +230,17 @@ async def _run(ctx, params: dict, home: Path, discovery) -> dict:
|
|
|
211
230
|
subscription = value["subscription_id"]
|
|
212
231
|
if reason := await asyncio.to_thread(_control_refusal, home, sid):
|
|
213
232
|
raise GatewayRPCError(reason)
|
|
214
|
-
|
|
233
|
+
gauge = None
|
|
234
|
+
if params["action"] in {"snapshot", "watch"}:
|
|
235
|
+
try:
|
|
236
|
+
stats = await client.rpc("session.stats", session_id=sid)
|
|
237
|
+
after = await client.rpc("session.resume", session_id=sid)
|
|
238
|
+
if all(value.get(k) == after.get(k) for k in ("session_id", "authority_epoch", "replay_epoch", "execution_generation")):
|
|
239
|
+
gauge = stats.get("plan_gauge")
|
|
240
|
+
except GatewayRPCError:
|
|
241
|
+
pass # older owners have no stats capability
|
|
242
|
+
return {**_snapshot(value), "plan_gauge": gauge,
|
|
243
|
+
"reactions_supported": _verified_contract(endpoint, "message-reactions-v1")}
|
|
215
244
|
|
|
216
245
|
receipt = lock = None
|
|
217
246
|
try:
|
|
@@ -332,7 +361,7 @@ async def _run(ctx, params: dict, home: Path, discovery) -> dict:
|
|
|
332
361
|
if (event.get("type") in {"session.updated", "notification.show", "notification.clear"}
|
|
333
362
|
or event.get("execution_generation") == snap["execution_generation"]
|
|
334
363
|
or event.get("type") == "session.info" and event.get("payload", {}).get("execution_generation") == snap["execution_generation"]):
|
|
335
|
-
ctx.emit("event", event)
|
|
364
|
+
ctx.emit("event", _event(event))
|
|
336
365
|
sequence = replay["last_sequence"]
|
|
337
366
|
ctx.emit("replay_complete", {})
|
|
338
367
|
checked = asyncio.get_running_loop().time()
|
|
@@ -386,7 +415,7 @@ async def _run(ctx, params: dict, home: Path, discovery) -> dict:
|
|
|
386
415
|
if event.get("seq", 0) <= sequence:
|
|
387
416
|
continue
|
|
388
417
|
sequence = event["seq"]
|
|
389
|
-
ctx.emit("event", event)
|
|
418
|
+
ctx.emit("event", _event(event))
|
|
390
419
|
return result
|
|
391
420
|
except GatewayRPCError as exc:
|
|
392
421
|
if params["action"] == "react" and str(exc) not in {"invalid_params", "not_found", "permission_denied", "profile_mismatch"}:
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Closed native room operations. Attach only, with the Web principal's own grant."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
|
|
8
|
+
from ..errors import InvalidParams
|
|
9
|
+
from ..home import profile_home_param
|
|
10
|
+
from .gateway_sessions import _connect, _owner_refusal, _with_owner
|
|
11
|
+
|
|
12
|
+
_FIELDS = {
|
|
13
|
+
"capabilities": set(), "list": {"offset", "limit"}, "state": {"room_id", "include_disbanded"},
|
|
14
|
+
"log": {"room_id", "since_seq", "limit"}, "create": {"room_id", "name", "members"},
|
|
15
|
+
"rename": {"room_id", "event_id", "name"}, "disband": {"room_id", "cancel_id"},
|
|
16
|
+
"send": {"room_id", "event_id", "payload"}, "stop": {"room_id", "cancel_id"},
|
|
17
|
+
"retry": {"room_id", "member_id", "task_id", "execution_generation"},
|
|
18
|
+
"discard": {"room_id", "member_id", "task_id", "execution_generation"},
|
|
19
|
+
"approve": {"room_id", "member_id", "task_id", "execution_generation", "request_id", "choice"},
|
|
20
|
+
"attachment.upload": {"room_id", "upload_id", "kind", "name", "mime", "data_base64"},
|
|
21
|
+
"attachment.download": {"room_id", "event_id", "attachment_id"},
|
|
22
|
+
}
|
|
23
|
+
_READS = {"capabilities", "list", "state", "log", "attachment.download"}
|
|
24
|
+
_REFUSALS = {"permission_denied", "profile_mismatch", "invalid_params", "stale_generation", "unknown_execution",
|
|
25
|
+
"unsupported_operation", "session_busy", "runtime_coordination_required"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _result(state, reason=None, value=None):
|
|
29
|
+
return {"state": state, "reason": reason, "value": value}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _run(home, subject, operation, discovery, expected_room=None):
|
|
33
|
+
from hermes_cli.gateway_client import GatewayRPCError
|
|
34
|
+
action = operation["action"]
|
|
35
|
+
method = "groups." + action
|
|
36
|
+
dispatched = False
|
|
37
|
+
try:
|
|
38
|
+
if discovery.state != "ready":
|
|
39
|
+
return _result("unavailable", "stopped")
|
|
40
|
+
principal = 'auth:v1:' + json.dumps(['talaria', '', subject], separators=(',', ':'))
|
|
41
|
+
async with _connect(home, discovery.endpoint, subject=principal) as (client, _):
|
|
42
|
+
# A native 15 MB attachment expands to 20 MB on the wire. Keep the shared native client,
|
|
43
|
+
# with a bounded receiver large enough for the owner's supported attachment envelope.
|
|
44
|
+
client.websocket.protocol.max_size = 24 * 1024 * 1024
|
|
45
|
+
described = await client.rpc("runtime.describe")
|
|
46
|
+
if _owner_refusal(described, discovery.endpoint):
|
|
47
|
+
return _result("unavailable", "unsupported")
|
|
48
|
+
caps = await client.rpc("groups.capabilities")
|
|
49
|
+
if (not isinstance(caps, dict) or type(caps.get("driver")) is not bool
|
|
50
|
+
or not isinstance(caps.get("methods"), list) or method not in caps["methods"]):
|
|
51
|
+
return _result("unavailable", "unsupported")
|
|
52
|
+
if not caps["driver"]:
|
|
53
|
+
return _result("unavailable", "driver_inactive")
|
|
54
|
+
if action == "capabilities":
|
|
55
|
+
return _result("available", value=caps)
|
|
56
|
+
if expected_room is not None:
|
|
57
|
+
current = await client.rpc("groups.state", room_id=expected_room["room_id"], include_disbanded=True)
|
|
58
|
+
room = current.get("room", {})
|
|
59
|
+
if (room.get("room_id") != expected_room["room_id"]
|
|
60
|
+
or room.get("authority_gateway_id") != expected_room["gateway_id"]
|
|
61
|
+
or room.get("authority_epoch") != expected_room["authority_epoch"]):
|
|
62
|
+
return _result("refused", "refused")
|
|
63
|
+
if action == "send":
|
|
64
|
+
operation = {**operation, "payload": {**operation["payload"], "thread_id": operation["event_id"]}}
|
|
65
|
+
dispatched = True
|
|
66
|
+
value = await client.rpc(method, **{k: v for k, v in operation.items() if k != "action"}, _timeout=25)
|
|
67
|
+
if not isinstance(value, dict) or len(json.dumps(value).encode()) > 24 * 1024 * 1024:
|
|
68
|
+
return _result("unknown" if action not in _READS else "unavailable", "unknown")
|
|
69
|
+
return _result("available", value=value)
|
|
70
|
+
except GatewayRPCError as exc:
|
|
71
|
+
reason = str(exc)
|
|
72
|
+
# Native errors are data, never public diagnostics. Only fixed refusals prove non-admission.
|
|
73
|
+
if reason in _REFUSALS:
|
|
74
|
+
return _result("refused", "refused")
|
|
75
|
+
if "method" in reason.lower() and not dispatched:
|
|
76
|
+
return _result("unavailable", "unsupported")
|
|
77
|
+
return _result("unknown" if dispatched and action not in _READS else "unavailable", "unknown")
|
|
78
|
+
except Exception: # noqa: BLE001 - a transport loss never authorizes automatic replay
|
|
79
|
+
return _result("unknown" if dispatched and action not in _READS else "unavailable", "unknown")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _read(ctx, coroutine):
|
|
83
|
+
task = asyncio.create_task(coroutine)
|
|
84
|
+
try:
|
|
85
|
+
while not task.done():
|
|
86
|
+
await asyncio.wait({task}, timeout=.1)
|
|
87
|
+
ctx.check_cancelled()
|
|
88
|
+
return await task
|
|
89
|
+
finally:
|
|
90
|
+
if not task.done():
|
|
91
|
+
task.cancel()
|
|
92
|
+
with suppress(asyncio.CancelledError):
|
|
93
|
+
await task
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def groups(ctx, params):
|
|
97
|
+
operation, subject = params.get("operation"), params.get("subject")
|
|
98
|
+
if (set(params) - {"profile_home", "subject", "operation", "expected_room"}
|
|
99
|
+
or not {"profile_home", "subject", "operation"} <= set(params)
|
|
100
|
+
or not isinstance(subject, str) or not 0 < len(subject) <= 128
|
|
101
|
+
or not isinstance(operation, dict) or operation.get("action") not in _FIELDS
|
|
102
|
+
or set(operation) - (_FIELDS[operation["action"]] | {"action"})):
|
|
103
|
+
raise InvalidParams("invalid room operation")
|
|
104
|
+
expected = params.get("expected_room")
|
|
105
|
+
if expected is not None and (not isinstance(expected, dict)
|
|
106
|
+
or set(expected) != {"room_id", "gateway_id", "authority_epoch"}
|
|
107
|
+
or expected.get("room_id") != operation.get("room_id")
|
|
108
|
+
or not isinstance(expected.get("gateway_id"), str)
|
|
109
|
+
or type(expected.get("authority_epoch")) is not int):
|
|
110
|
+
raise InvalidParams("invalid room authority fence")
|
|
111
|
+
home = profile_home_param(params).resolve()
|
|
112
|
+
ctx.check_cancelled()
|
|
113
|
+
def run(discovery):
|
|
114
|
+
coroutine = _run(home, subject, operation, discovery, expected)
|
|
115
|
+
return _read(ctx, coroutine) if operation["action"] in _READS else coroutine
|
|
116
|
+
result = _with_owner(home, run)
|
|
117
|
+
if "value" not in result:
|
|
118
|
+
return _result("unavailable", "stopped" if result["state"] == "stopped" else "unsupported"
|
|
119
|
+
if result["state"] == "incompatible" else "unavailable")
|
|
120
|
+
return result
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from contextlib import contextmanager
|
|
5
6
|
import errno
|
|
6
7
|
import hashlib
|
|
7
8
|
import os
|
|
@@ -45,22 +46,45 @@ def _digest(path: Path) -> str | None:
|
|
|
45
46
|
return hashlib.sha256(handle.read()).hexdigest()
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
@contextmanager
|
|
50
|
+
def file_lock(path: Path, *, parent_fd=None):
|
|
51
|
+
"""The native memory lock, with an anchored opening for descriptor-relative publishers."""
|
|
52
|
+
if parent_fd is None:
|
|
53
|
+
from tools.memory_tool_store import MemoryStore
|
|
54
|
+
native = getattr(MemoryStore, '_file_lock', None)
|
|
55
|
+
with native(path) if native else _SAVE_LOCK:
|
|
56
|
+
yield
|
|
57
|
+
return
|
|
58
|
+
import fcntl
|
|
59
|
+
# Same persistent .lock inode and flock protocol as MemoryStore._file_lock; never re-resolve its parent.
|
|
60
|
+
with _SAVE_LOCK:
|
|
61
|
+
fd = os.open(path.name + '.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600, dir_fd=parent_fd)
|
|
62
|
+
with os.fdopen(fd, 'r+') as lock:
|
|
63
|
+
info = os.fstat(lock.fileno())
|
|
64
|
+
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
|
|
65
|
+
raise OSError('Unsafe memory lock')
|
|
66
|
+
os.fchmod(lock.fileno(), 0o600)
|
|
67
|
+
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
|
|
68
|
+
try:
|
|
69
|
+
yield
|
|
70
|
+
finally:
|
|
71
|
+
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
|
|
72
|
+
|
|
73
|
+
|
|
48
74
|
def write(home: Path, section: str, base: str, content: str) -> dict:
|
|
49
75
|
"""Replace ``section``'s file with ``content`` only while its bytes still hash to ``base``, the server's read.
|
|
50
76
|
|
|
51
77
|
Each file is checked and written under the Agent's own per-file lock (``<file>.lock``), so neither a memory tool
|
|
52
78
|
call nor a concurrent save can land between the two; an Agent without the lock helper gets this sidecar's lock.
|
|
53
79
|
"""
|
|
54
|
-
from tools.memory_tool_store import MemoryStore
|
|
55
80
|
from utils import atomic_write_text
|
|
56
81
|
|
|
57
82
|
if section not in ("memory", "user", "soul"):
|
|
58
83
|
raise InvalidParams("section must be memory, user, or soul")
|
|
59
84
|
# The files the server read: SOUL.md in the profile home, MEMORY.md / USER.md in its memories folder.
|
|
60
85
|
path = home / "SOUL.md" if section == "soul" else home / "memories" / ("USER.md" if section == "user" else "MEMORY.md")
|
|
61
|
-
file_lock = getattr(MemoryStore, "_file_lock", None)
|
|
62
86
|
try:
|
|
63
|
-
with file_lock(path)
|
|
87
|
+
with file_lock(path):
|
|
64
88
|
if _digest(path) != base:
|
|
65
89
|
return {"written": False}
|
|
66
90
|
atomic_write_text(path, content, tmp_prefix=".mem_", preserve_mode=True)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Safe, read-only account/handoff projection shared by OAuth and provider status.
|
|
2
|
+
|
|
3
|
+
The Agent owns the record and retry worker. This bounded display cache only retains a
|
|
4
|
+
confirmed view across read failures, keyed by the current profile and account.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import re
|
|
10
|
+
import threading
|
|
11
|
+
from collections import OrderedDict
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
_LAST: OrderedDict[str, dict] = OrderedDict()
|
|
16
|
+
_LOCK = threading.RLock()
|
|
17
|
+
_STATES = {"idle", "pending", "retrying", "claimed", "gone", "failed", "paused"}
|
|
18
|
+
_ERRORS = {"client_not_allowed", "account_locked", "invalid_request", "retry_window_ended"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _identity(home: Path) -> tuple[str, str | None] | None:
|
|
22
|
+
from hermes_cli.anon_auth import is_guest_state
|
|
23
|
+
from hermes_cli.auth import get_provider_auth_state
|
|
24
|
+
|
|
25
|
+
state = get_provider_auth_state("nous")
|
|
26
|
+
if not isinstance(state, dict) or is_guest_state(state):
|
|
27
|
+
return None
|
|
28
|
+
return _token_identity(home, state.get("access_token"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _token_identity(home: Path, token: Any) -> tuple[str, str | None] | None:
|
|
32
|
+
from hermes_cli.auth_constants import _decode_jwt_claims
|
|
33
|
+
|
|
34
|
+
claims = _decode_jwt_claims(token)
|
|
35
|
+
subject = claims.get("sub")
|
|
36
|
+
if not isinstance(subject, str) or not subject:
|
|
37
|
+
return None
|
|
38
|
+
identity = hashlib.sha256(f"{home.resolve()}\0{subject}".encode()).hexdigest()
|
|
39
|
+
email = claims.get("email")
|
|
40
|
+
if not isinstance(email, str) or not re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", email) or len(email) > 254:
|
|
41
|
+
return identity, None
|
|
42
|
+
local, domain = email.rsplit("@", 1)
|
|
43
|
+
return identity, f"{local[0]}***@{domain}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _apps(value: Any) -> list[str]:
|
|
47
|
+
if not isinstance(value, (list, tuple)):
|
|
48
|
+
return []
|
|
49
|
+
return list(dict.fromkeys(v for v in value[:100] if isinstance(v, str) and re.fullmatch(r"[a-z0-9][a-z0-9_-]{0,63}", v)))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def read(home: Path, completed: Any = None) -> dict | None:
|
|
53
|
+
"""One sequential read owner for flow/provider consumers, called under scoped_home."""
|
|
54
|
+
with _LOCK:
|
|
55
|
+
return _read(home, completed)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _read(home: Path, completed: Any = None) -> dict | None:
|
|
59
|
+
"""No provisioning, refresh, drain, or retry dispatch."""
|
|
60
|
+
try:
|
|
61
|
+
from hermes_cli import guest_handoff as native
|
|
62
|
+
except ImportError:
|
|
63
|
+
return None # older Agents keep generic success
|
|
64
|
+
try:
|
|
65
|
+
identity = _identity(home)
|
|
66
|
+
except Exception: # no current identity proof means no cached account may be displayed
|
|
67
|
+
return None
|
|
68
|
+
if identity is None:
|
|
69
|
+
return None
|
|
70
|
+
account_id, email = identity
|
|
71
|
+
try:
|
|
72
|
+
status = native.handoff_status()
|
|
73
|
+
if status.get("state") == "idle" and completed is not None:
|
|
74
|
+
status = getattr(completed, "handoff_status", None) or status
|
|
75
|
+
state = status.get("state") if status.get("state") in _STATES else "unknown"
|
|
76
|
+
# The public native read only fences *active* records. Prove terminal ownership
|
|
77
|
+
# with the same native selector/binding helper before exposing any old app names.
|
|
78
|
+
if state not in {"idle", "unknown", "paused"}:
|
|
79
|
+
records = native._load(native._records_path())["records"].values()
|
|
80
|
+
mine = [r for r in records if native._home_key() in r.get("profiles", ())]
|
|
81
|
+
record = max(mine, key=lambda r: str(r.get("updated_at") or "")) if mine else None
|
|
82
|
+
if record is not None and native._status_of(record) != status:
|
|
83
|
+
raise RuntimeError("Native handoff changed during read")
|
|
84
|
+
if record is None or native._account_bound_to(record) is None:
|
|
85
|
+
state = "paused" if record else "unknown"
|
|
86
|
+
safe = {"state": state, "account_email": email,
|
|
87
|
+
"connectors_kind": status.get("connectors_kind") if status.get("connectors_kind") in {"moved", "swapped", "none", "conflict"} else None,
|
|
88
|
+
"moved_toolkits": _apps(status.get("moved_toolkits")) if state == "claimed" else [],
|
|
89
|
+
"dropped_toolkits": _apps(status.get("dropped_toolkits")) if state == "claimed" else [],
|
|
90
|
+
"probably_moved": status.get("probably_moved") is True,
|
|
91
|
+
"error": status.get("error") if status.get("error") in _ERRORS else None}
|
|
92
|
+
lines = native.wire_status(safe)["lines"] if state != "unknown" else ["Connected-app transfer status is unknown."]
|
|
93
|
+
if _identity(home) != identity:
|
|
94
|
+
return None
|
|
95
|
+
view = {"account_id": account_id, "email": email, "state": state, "lines": lines,
|
|
96
|
+
"moved": safe["moved_toolkits"], "dropped": safe["dropped_toolkits"],
|
|
97
|
+
"reconnect": safe["dropped_toolkits"],
|
|
98
|
+
"poll_after": 5 if state in {"pending", "retrying", "unavailable"} else None}
|
|
99
|
+
with _LOCK:
|
|
100
|
+
_LAST[account_id] = view
|
|
101
|
+
_LAST.move_to_end(account_id)
|
|
102
|
+
while len(_LAST) > 32:
|
|
103
|
+
_LAST.popitem(last=False)
|
|
104
|
+
return view
|
|
105
|
+
except Exception: # raw native records and exception details never cross this boundary
|
|
106
|
+
try:
|
|
107
|
+
if _identity(home) != identity:
|
|
108
|
+
return None
|
|
109
|
+
except Exception:
|
|
110
|
+
return None
|
|
111
|
+
with _LOCK:
|
|
112
|
+
previous = _LAST.get(account_id)
|
|
113
|
+
return {**(previous or {"account_id": account_id, "email": email, "moved": [], "dropped": [], "lines": []}),
|
|
114
|
+
"state": "unavailable", "reconnect": [], "poll_after": 5,
|
|
115
|
+
"lines": [*(previous or {}).get("lines", []), "Connected-app transfer status is unavailable."]}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def connector_headers(home: Path, account_id: str, url: str) -> dict:
|
|
119
|
+
"""The native connector client reads this on every request, never provisioning or refreshing."""
|
|
120
|
+
from tools.managed_gateway_auth import managed_gateway_auth_headers
|
|
121
|
+
from tools.managed_tool_gateway import peek_nous_access_token
|
|
122
|
+
|
|
123
|
+
def token():
|
|
124
|
+
value = peek_nous_access_token()
|
|
125
|
+
current, bearer = _identity(home), _token_identity(home, value)
|
|
126
|
+
if current is None or bearer is None or current[0] != account_id or bearer[0] != account_id:
|
|
127
|
+
return None
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
return managed_gateway_auth_headers(url, token_reader=token)
|
|
@@ -26,7 +26,7 @@ from typing import Any, Callable
|
|
|
26
26
|
from ..errors import InvalidParams, RpcError
|
|
27
27
|
from ..home import profile_home_param, scoped_home
|
|
28
28
|
from ..rpc import CallContext
|
|
29
|
-
from . import plugin_oauth
|
|
29
|
+
from . import plugin_oauth, nous_account
|
|
30
30
|
|
|
31
31
|
log = logging.getLogger("talaria_sidecar.oauth")
|
|
32
32
|
|
|
@@ -65,6 +65,7 @@ class Flow:
|
|
|
65
65
|
# The credential is saved but the sign-in has not finished (a guest sign-in still settles the account): a cancel or
|
|
66
66
|
# an expiry can no longer undo it, and the flow stays pending until its own ending.
|
|
67
67
|
committed: bool = False
|
|
68
|
+
nous_account_id: str | None = None
|
|
68
69
|
# A browser sign-in also finishes from the redirect address the user pastes (``oauth.submit_redirect``).
|
|
69
70
|
submit: Callable[[str], None] | None = None
|
|
70
71
|
# Set once a browser sign-in's loopback listener is closed, so a replacing start can bind the same fixed port.
|
|
@@ -78,7 +79,12 @@ class Flow:
|
|
|
78
79
|
self.status, self.error, self.ended_at = status, error, time.time()
|
|
79
80
|
|
|
80
81
|
def view(self) -> dict:
|
|
81
|
-
|
|
82
|
+
view = {"flow_id": self.flow_id, "provider": self.provider, "status": self.status, "error": self.error}
|
|
83
|
+
if self.status == "approved" and self.nous_account_id:
|
|
84
|
+
with scoped_home(self.home):
|
|
85
|
+
account = nous_account.read(self.home)
|
|
86
|
+
view["nous_account"] = account if account and account["account_id"] == self.nous_account_id else None
|
|
87
|
+
return view
|
|
82
88
|
|
|
83
89
|
|
|
84
90
|
class _Watched:
|
|
@@ -156,6 +162,9 @@ def _nous_begin() -> tuple[dict, dict]:
|
|
|
156
162
|
|
|
157
163
|
# v0.22.0 replaced ``guest_enabled`` with ``guest_allowed``; 0.21.x releases only have the former.
|
|
158
164
|
guest_gate = getattr(anon_auth, "guest_allowed", None) or getattr(anon_auth, "guest_enabled")
|
|
165
|
+
native = getattr(anon_auth, "run_sign_in", None)
|
|
166
|
+
if native and "replace_account" in inspect.signature(native).parameters:
|
|
167
|
+
return _nous_sign_in(anon_auth)
|
|
159
168
|
if guest_gate() and anon_auth.has_guest():
|
|
160
169
|
# The native sign-in owns the guest's connector handoff for its installed protocol.
|
|
161
170
|
if hasattr(anon_auth, "run_sign_in"):
|
|
@@ -191,6 +200,9 @@ def _nous_sign_in(anon_auth: Any) -> tuple[dict, dict]:
|
|
|
191
200
|
yield may_persist
|
|
192
201
|
if may_persist:
|
|
193
202
|
flow.committed = True
|
|
203
|
+
# Bind before native handoff/settlement releases this account to another sign-in.
|
|
204
|
+
account = nous_account.read(flow.home)
|
|
205
|
+
state["nous_account_id"] = account["account_id"] if account else None
|
|
194
206
|
|
|
195
207
|
def scope():
|
|
196
208
|
flow = state.get("flow")
|
|
@@ -198,11 +210,13 @@ def _nous_sign_in(anon_auth: Any) -> tuple[dict, dict]:
|
|
|
198
210
|
|
|
199
211
|
# Older Agents use promotion; v0.22.5 removed its option in favor of plain device login and native handoff.
|
|
200
212
|
options = {"cancel_wins_after_promotion": True} if "cancel_wins_after_promotion" in inspect.signature(anon_auth.run_sign_in).parameters else {}
|
|
213
|
+
if "replace_account" in inspect.signature(anon_auth.run_sign_in).parameters:
|
|
214
|
+
options["replace_account"] = True
|
|
201
215
|
sign_in = anon_auth.run_sign_in(cancelled=cancelled, persist_guard=persist_guard, scope=scope, **options)
|
|
202
216
|
first = next(sign_in)
|
|
203
217
|
if first.kind != "code":
|
|
204
218
|
sign_in.close()
|
|
205
|
-
raise RpcError(first.
|
|
219
|
+
raise RpcError(first.copy, condition="oauth_unsupported" if first.precondition else "oauth_failed")
|
|
206
220
|
state["sign_in"] = sign_in
|
|
207
221
|
return {"user_code": first.code, "verification_url": first.link, "expires_in": first.expires_in, "interval": first.interval}, state
|
|
208
222
|
|
|
@@ -226,6 +240,8 @@ def _nous_wait(flow: Flow, state: dict) -> dict:
|
|
|
226
240
|
sign_in.close()
|
|
227
241
|
if not ended.ok:
|
|
228
242
|
raise _SignInEnded(ended)
|
|
243
|
+
with scoped_home(flow.home):
|
|
244
|
+
nous_account.read(flow.home, ended)
|
|
229
245
|
return {}
|
|
230
246
|
from hermes_cli.auth import _poll_for_token
|
|
231
247
|
|
|
@@ -406,6 +422,7 @@ def _run(flow: Flow, wait: Callable[[Flow, dict], dict], save: Callable[[Flow, d
|
|
|
406
422
|
return
|
|
407
423
|
with scoped_home(flow.home):
|
|
408
424
|
save(flow, state, tokens)
|
|
425
|
+
flow.nous_account_id = state.get("nous_account_id")
|
|
409
426
|
flow.end("approved")
|
|
410
427
|
log.info("oauth: %s sign-in approved (flow=%s)", flow.provider, flow.flow_id[:6])
|
|
411
428
|
except _Stop:
|
|
@@ -556,7 +573,11 @@ def register(reg) -> None:
|
|
|
556
573
|
|
|
557
574
|
@reg.method("oauth.poll", requires_agent=False)
|
|
558
575
|
def poll_(ctx: CallContext, params: dict) -> dict:
|
|
559
|
-
|
|
576
|
+
home, flow_id = profile_home_param(params), _flow_id(params)
|
|
577
|
+
flow = _flow(home, flow_id)
|
|
578
|
+
if flow.status == "approved" and flow.nous_account_id:
|
|
579
|
+
reg.runtime.ensure_current() # a retired worker cannot promise background progress
|
|
580
|
+
return poll(home, flow_id)
|
|
560
581
|
|
|
561
582
|
@reg.method("oauth.cancel", requires_agent=False)
|
|
562
583
|
def cancel_(ctx: CallContext, params: dict) -> dict:
|
|
@@ -27,25 +27,7 @@ _STATES = {'draft', 'approved', 'sent', 'saved', 'failed', 'discarded'}
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _producer(home):
|
|
30
|
-
|
|
31
|
-
try:
|
|
32
|
-
from hermes_cli import plugins_cmd as pc
|
|
33
|
-
metadata = pc._read_install_metadata()
|
|
34
|
-
for plugin in dashboard_plugins._plugins(home):
|
|
35
|
-
if plugin['name'] != 'draft-cards' or not plugin['enabled'] or plugin.get('source') != 'user':
|
|
36
|
-
continue
|
|
37
|
-
root, anchor = Path(plugin['_dir']).parent, Path(plugin['_asset_anchor'])
|
|
38
|
-
record = metadata.get(root.name, {})
|
|
39
|
-
catalog = record.get('catalog', {})
|
|
40
|
-
if catalog.get('name') != 'draft-cards' or catalog.get('sha') != _PIN or record.get('revision') != _PIN:
|
|
41
|
-
continue
|
|
42
|
-
if not all((body := dashboard_plugins._read(root, name, anchor=anchor)) is not None
|
|
43
|
-
and hashlib.sha256(body).hexdigest() == digest for name, digest in _SOURCES.items()):
|
|
44
|
-
continue
|
|
45
|
-
return _PIN
|
|
46
|
-
except (ImportError, AttributeError, OSError, ValueError, TypeError):
|
|
47
|
-
pass
|
|
48
|
-
return None
|
|
30
|
+
return dashboard_plugins.reviewed_producer(home, 'draft-cards', _PIN, _SOURCES)
|
|
49
31
|
|
|
50
32
|
|
|
51
33
|
def _database(home):
|