@maudecode/talaria-web 1.21.0 → 1.23.2
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 +225 -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.d.ts +1 -1
- package/dist/api/raw-routes.js +15 -3
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +41 -0
- package/dist/api/router.js +2 -0
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +590 -160
- package/dist/api/sessions-router.js +14 -14
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +948 -1
- package/dist/api/settings-router.js +54 -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 +69 -9
- 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/cli/ctl.d.ts +5 -2
- package/dist/cli/ctl.js +90 -42
- package/dist/cli/ctl.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 +21 -0
- package/dist/feedback.js +328 -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 +10 -1
- 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 +10 -2
- package/dist/providers/catalog.js +113 -15
- package/dist/providers/catalog.js.map +1 -1
- package/dist/redact.js +9 -3
- package/dist/redact.js.map +1 -1
- package/dist/release.d.ts +14 -1
- package/dist/release.js +40 -2
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +29 -5
- 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 +8 -3
- package/dist/sessions/list.js +17 -6
- 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 +34 -2
- package/dist/sessions/relay.js +84 -7
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +7 -2
- package/dist/sessions/service.js +106 -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 +3 -1
- package/dist/sessions/shares.js +33 -11
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.js +2 -0
- package/dist/sessions/source-kind.js.map +1 -1
- package/dist/sessions/state-db.d.ts +2 -1
- package/dist/sessions/state-db.js +12 -5
- 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 +7 -2
- package/dist/sessions/streams.js +16 -6
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +2 -0
- package/dist/sessions/titles.js +11 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/todo.js +10 -1
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/turn.js +50 -21
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.js +6 -1
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +3 -0
- package/dist/sidecar/client.js +2 -1
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/discover.js +3 -2
- package/dist/sidecar/discover.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/insights.d.ts +3 -0
- package/dist/tools/insights.js +6 -7
- package/dist/tools/insights.js.map +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/update-notifications.d.ts +2 -0
- package/dist/tools/update-notifications.js +7 -3
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +26 -9
- package/dist/tools/updates.js +144 -47
- 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/media-refs.js +10 -3
- package/dist/workspace/media-refs.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 +4 -3
- package/sidecar/agent_importer.json +8 -0
- package/sidecar/scripts/agent_importer_contract.py +19 -0
- package/sidecar/scripts/suite_runner.py +55 -8
- 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 +154 -143
- package/static/dist/THIRD_PARTY_LICENSES.md +1 -1
- package/static/dist/assets/AppShell-jwd95ofg.js +8 -0
- package/static/dist/assets/{Button-nuaikybd.js → Button-nrcj7shj.js} +1 -1
- package/static/dist/assets/ChatPage-fmgk8xpk.js +2 -0
- package/static/dist/assets/Composer-oya4ggu7.js +5 -0
- package/static/dist/assets/ConnectTargets-rg90uiui.js +1 -0
- package/static/dist/assets/Dialog-gk0uatl8.js +1 -0
- package/static/dist/assets/ExtensionRoute-ji4xce9l.js +1 -0
- package/static/dist/assets/HubRoute-2pgy4aa2.js +2 -0
- package/static/dist/assets/Markdown-d6br42ts.js +2 -0
- package/static/dist/assets/{MarkdownRenderer-ccgqrza3.js → MarkdownRenderer-k8rmb83g.js} +6 -6
- package/static/dist/assets/OAuthSignIn-pbls9ljk.js +1 -0
- package/static/dist/assets/QueueCard-liib58ru.js +1 -0
- package/static/dist/assets/ResourceSwitch-gtdv2jgs.js +1 -0
- package/static/dist/assets/RightPanel-b89e4kx3.js +1 -0
- package/static/dist/assets/Select-l6qybenp.js +1 -0
- package/static/dist/assets/SessionListPanel-l4vy95j8.js +1 -0
- package/static/dist/assets/SettingsLayout-chw7pl2m.js +1 -0
- package/static/dist/assets/TerminalPanel-qrd0gkx7.js +16 -0
- package/static/dist/assets/Toaster-ihzgaq9z.js +1 -0
- package/static/dist/assets/_app-cn1au4zq.js +1 -0
- package/static/dist/assets/{_app.ext._extensionId-n797jp72.js → _app.ext._extensionId-m4npkrr0.js} +1 -1
- package/static/dist/assets/_app.groups-c8ycbp5n.js +1 -0
- package/static/dist/assets/{_app.index-ne43mdw2.js → _app.index-f1fckz3h.js} +1 -1
- package/static/dist/assets/{_app.insights-fqonovb4.js → _app.insights-ezivk7ad.js} +1 -1
- package/static/dist/assets/{_app.kanban-kysliv19.js → _app.kanban-zbkeov0f.js} +1 -1
- package/static/dist/assets/{_app.logs-febut82r.js → _app.logs-dqam5zyz.js} +1 -1
- package/static/dist/assets/{_app.memory-ha86oj1c.js → _app.memory-gzxkm3a0.js} +1 -1
- package/static/dist/assets/{_app.profiles-cnu3gxxq.js → _app.profiles-j28l31hl.js} +1 -1
- package/static/dist/assets/{_app.session._sessionId-ifzjpcgn.js → _app.session._sessionId-55egsufs.js} +1 -1
- package/static/dist/assets/{_app.settings-nmakdiaf.js → _app.settings-f5ltpieb.js} +1 -1
- package/static/dist/assets/_app.settings._section-dda0vfot.js +3 -0
- package/static/dist/assets/{_app.skills-f3wad8yo.js → _app.skills-kuc81hq0.js} +1 -1
- package/static/dist/assets/_app.tasks-gyy6vj5l.js +1 -0
- package/static/dist/assets/{_app.todos-bml1m3r1.js → _app.todos-d0xoy68t.js} +1 -1
- package/static/dist/assets/{_app.workspaces-f3d066lx.js → _app.workspaces-pawp2gxk.js} +1 -1
- package/static/dist/assets/_delete-qnxuo87s.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-i53jc4yp.js → abnfDiagram-VCTEODGH-mhav7n5a.js} +1 -1
- package/static/dist/assets/{architecture-7GRP2DOG-e3bv5m6f.js → architecture-7GRP2DOG-ozbanukt.js} +1 -1
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-pakekxu2.js → architectureDiagram-5GKGNRK7-febtor5m.js} +1 -1
- package/static/dist/assets/archive-gyk9c34a.js +1 -0
- package/static/dist/assets/autoReadClaim-nlyv8tu9.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bc6bo1n6.js → blockDiagram-I7D4REHJ-ocais2jf.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-btvwk2qx.js → c4Diagram-7LVT6UL2-cgpfjx5f.js} +1 -1
- package/static/dist/assets/channel-els5ri1f.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-dlwab11c.js → chunk-4HAMMTFA-hvqdiiow.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-dp5gh7i8.js → chunk-75Z2AOVW-hxbulvtb.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bi06zl1c.js → chunk-DU6HZSFF-kkp6a7gp.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-kvv1rnlh.js → chunk-F27PBJKO-ljz0gssc.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ji0rx9by.js → chunk-GMAD6QVW-bo0enyxn.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-nmz0j82p.js → chunk-GVQU2GXP-cnq7l2v6.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-kmi1qx88.js → chunk-IMKFNOWR-gjnkrfu6.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-xhb7mebj.js → chunk-L3NEJ4N5-gjfp71qi.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-5a9sgscq.js → chunk-OSK3NFVY-ieq5r94b.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-dfezp4vu.js → chunk-P2QGCYS3-hdefjk56.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-gd2vq3c0.js → chunk-POPQ4Y6H-mcbt8wpr.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-wvw2nksf.js → chunk-PWAF6VOD-mx2drja9.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-f58q97px.js → chunk-SHT3W25Y-j7dd9i66.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-abgwpyz9.js → chunk-SVP7TREG-jcnt3lxt.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-h4sbs8ds.js → chunk-TICWLB2K-lrwv5hx9.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-krpt9pdd.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-krpt9pdd.js +1 -0
- package/static/dist/assets/completionAudio-fhxf0h9j.js +1 -0
- package/static/dist/assets/{copy-m9h56qa8.js → copy-gyzrqmyr.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-g081vffs.js → cynefin-OW5HDTMX-heq36srt.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-odxvjesk.js → cynefinDiagram-5FMLGOSQ-crrmq338.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cpdwzjs4.js → dagre-GXQ25YYZ-m7fpnt6k.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-clv5mxvu.js → diagram-S7CK7UJ4-ebm1ebv6.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-m2ia1mbf.js → diagram-UQ7AKVKN-hc0ypsp1.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-ea6zv636.js → diagram-VSXAHHWV-mu8q5n4b.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-oqlef685.js → diagram-VX7I27RA-i488wdlb.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-kjubcbwu.js → diagram-Z3DM3KII-gz3nwvq9.js} +1 -1
- package/static/dist/assets/{dist-carq1azj.js → dist-ge7hom9n.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-lwskecx4.js} +1 -1
- package/static/dist/assets/{erDiagram-RLTQ6QDP-eb7pmk3x.js → erDiagram-RLTQ6QDP-c0bysigr.js} +1 -1
- package/static/dist/assets/{eventmodeling-NTZA5JFV-gvi1fxhn.js → eventmodeling-NTZA5JFV-k19e509j.js} +1 -1
- package/static/dist/assets/feedbackVideo.worker-JBVT3sDD.js +480 -0
- package/static/dist/assets/flowDiagram-HODETNUW-n4m8e6kq.js +1 -0
- package/static/dist/assets/folder-plus-m1ywe0cv.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-jmisowrc.js → ganttDiagram-EL5Y4UJY-mz1p8yzt.js} +1 -1
- package/static/dist/assets/getPseudoElementBounds-ci0x9gki.js +1 -0
- package/static/dist/assets/{gitGraph-4MIJSDKK-m7kt3u6e.js → gitGraph-4MIJSDKK-h0uskiog.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-nq7vfzh2.js → gitGraphDiagram-WWUBYQGX-e4d9um1j.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-d47n6oqu.js} +1 -1
- package/static/dist/assets/{host-ipp4e84c.js → host-f7eudvft.js} +1 -1
- package/static/dist/assets/index-lab8g9gs.css +2 -0
- package/static/dist/assets/index-n5rg2q2r.js +13 -0
- package/static/dist/assets/{info-A6RAGUB7-idazwfqe.js → info-A6RAGUB7-k04of9c6.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-bb9jh8gl.js → infoDiagram-27XIBGKW-l98n1tgz.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-nqc132rv.js → ishikawaDiagram-5VMMS53U-gkznu2d6.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-fpvnhy44.js → journeyDiagram-3NMN7TZE-mt23qvx1.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-ndzqm6fu.js → kanban-definition-UXKFOSKX-ab93l7y5.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-dt9efiqk.js} +1 -1
- package/static/dist/assets/{line-g625e7cj.js → line-dkm9r3as.js} +1 -1
- package/static/dist/assets/link-2-ozyjqwhh.js +1 -0
- package/static/dist/assets/{link-gfdaho08.js → link-dip3g6zs.js} +1 -1
- 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-go5xfzkx.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-o7wh7c74.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-plzkuzov.js → mermaid-parser.core-c4ywstu7.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-ojrgs569.js → mindmap-definition-YA3MSWOX-mnvkgz5u.js} +1 -1
- package/static/dist/assets/onboarding-hbbpvp6w.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-gyaf9s8h.js → packet-AYTQ26CC-dfm2ygox.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bf6xj8mq.js → pegDiagram-XKGWAZYB-kfbyls4v.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-f5ymzelo.js → pie-WAS4IAKB-jniw7r2b.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-d0qjl3zi.js → pieDiagram-E7YTZNPT-cir9nlg4.js} +1 -1
- package/static/dist/assets/presence-ivo54s19.js +1 -0
- package/static/dist/assets/prompt_queue_cancelled-cebcuagu.js +1 -0
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-oqy1mras.js → quadrantDiagram-AXDQQJYC-2nnb2u4m.js} +1 -1
- package/static/dist/assets/queryKeys-mbi6o5vh.js +9 -0
- package/static/dist/assets/{radar-RG4KPBEZ-b6jcjvda.js → radar-RG4KPBEZ-jfvtqhq2.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-e3a6lk5k.js → railroad-74A4TZTK-hnm0ikx7.js} +1 -1
- package/static/dist/assets/{railroad-abnf-HS5TGJTU-ff5hocrx.js → railroad-abnf-HS5TGJTU-hcfz5gyc.js} +1 -1
- package/static/dist/assets/{railroad-ebnf-LZEXJU2U-exkhyw4n.js → railroad-ebnf-LZEXJU2U-ocbvmcwo.js} +1 -1
- package/static/dist/assets/{railroad-peg-WCYAUIDC-jefk9b7c.js → railroad-peg-WCYAUIDC-kkbzgo8d.js} +1 -1
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-e4amks1i.js → railroadDiagram-O6MQD6OU-ox5v7853.js} +1 -1
- package/static/dist/assets/rename-ccd52yws.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-c77k1e2y.js → requirementDiagram-BXWQKSXE-e1iq54h4.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-oa1cc52s.js → sankeyDiagram-P5KCCOFB-mmza9r2u.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-j20gqt22.js → sequenceDiagram-WJ2MYXX4-dioic69h.js} +1 -1
- package/static/dist/assets/{share._token-kmfoflw6.js → share._token-i51g4ccy.js} +1 -1
- package/static/dist/assets/sliders-horizontal-nj1dipq4.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-co2ghkdp.js → stateDiagram-D77RDMKH-do5wvqjw.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-f0sedx52.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-m30cktqe.js → swimlanes-42K2YHIH-mhjzgtgl.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n0x444kc.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-liuep6fl.js → timeline-definition-24CTP7MA-i5820ppg.js} +1 -1
- package/static/dist/assets/toast-esv4ma70.js +56 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-np5d2jv2.js → treeView-Q6P3EWNA-btp8feob.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-cmpltxh1.js → treemap-WGGIJYW6-lv71dt7p.js} +1 -1
- package/static/dist/assets/tts-bq7ax6xp.js +1 -0
- package/static/dist/assets/{useForm-f1udzsjf.js → useForm-d5wxx0l0.js} +1 -1
- package/static/dist/assets/{useParams-ierevvts.js → useParams-gk1u0y6m.js} +1 -1
- package/static/dist/assets/{useSearch-i7nr9ypl.js → useSearch-nsrme1pf.js} +1 -1
- package/static/dist/assets/useTranscript-m9ve9mml.js +1 -0
- package/static/dist/assets/{vennDiagram-4TSXK5OY-b2g42qrg.js → vennDiagram-4TSXK5OY-bffv7e11.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lb0eqbff.js → wardley-WFR3VGLG-f1m16ivx.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ermwaqha.js → wardleyDiagram-VM6X3IG4-ous4pq48.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dll0jldk.js → xychartDiagram-S5SC5T6Z-jxgbp4as.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/ExtensionRoute-l1yjrso4.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/refresh-cw-lfp4u1oh.js +0 -1
- 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
|
@@ -239,7 +239,7 @@ def forget_profile_mcp_tools(home) -> None:
|
|
|
239
239
|
_MCP_DISCOVERED_HOMES.discard(os.path.realpath(home))
|
|
240
240
|
|
|
241
241
|
|
|
242
|
-
def _reload_mcp() -> str:
|
|
242
|
+
def _reload_mcp(*, log_errors=True) -> str:
|
|
243
243
|
with _RELOAD_MCP_LOCK:
|
|
244
244
|
try:
|
|
245
245
|
from agent.secret_scope import is_multiplex_active
|
|
@@ -274,7 +274,8 @@ def _reload_mcp() -> str:
|
|
|
274
274
|
with _lock:
|
|
275
275
|
tools = [name for name in tools if _mcp_tool_server_names.get(name) in connected]
|
|
276
276
|
except Exception as exc: # noqa: BLE001
|
|
277
|
-
|
|
277
|
+
if log_errors:
|
|
278
|
+
log.warning("Failed to reload MCP servers", exc_info=True)
|
|
278
279
|
raise RpcError("Failed to reload MCP servers", condition="mcp_reload_failed") from exc
|
|
279
280
|
added, removed, reconnected = connected - old, old - connected, connected & old
|
|
280
281
|
lines = ["Reloaded MCP servers from configuration."]
|
|
@@ -846,11 +847,12 @@ def register(registry) -> None:
|
|
|
846
847
|
output = execute_agent_command(command, session_id, workspace)
|
|
847
848
|
source = "agent"
|
|
848
849
|
if output is None:
|
|
849
|
-
from .chat import _turn_identity
|
|
850
|
+
from .chat import _turn_identity, command_choices
|
|
850
851
|
|
|
851
852
|
# TAL-904: a chat's plugin command runs under that chat's session and workspace, as its turns do.
|
|
852
|
-
with _turn_identity(session_id, workspace) if session_id else contextlib.nullcontext():
|
|
853
|
+
with command_choices(ctx, params), (_turn_identity(session_id, workspace) if session_id else contextlib.nullcontext()):
|
|
853
854
|
output = execute_plugin_command(command, session_id)
|
|
855
|
+
ctx.check_cancelled()
|
|
854
856
|
source = "plugin"
|
|
855
857
|
if output is None:
|
|
856
858
|
raise RpcError(f"unknown command {parse_slash_command(command)[0]!r}", condition="command_not_found")
|
|
@@ -133,9 +133,8 @@ def _split_config_aliases(text: str, desired: dict) -> str:
|
|
|
133
133
|
return output.getvalue()
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
raise InvalidParams("config must be an object")
|
|
136
|
+
def assert_config_writable(profile_home: Path | None = None) -> None:
|
|
137
|
+
"""Verify the owning profile policy before any config publication; uncertainty refuses."""
|
|
139
138
|
try:
|
|
140
139
|
managed = importlib.import_module("hermes_constants").get_managed_system(profile_home)
|
|
141
140
|
except Exception as exc: # noqa: BLE001
|
|
@@ -144,6 +143,12 @@ def write_config(path: Path, config: dict, *, profile_home: Path | None = None)
|
|
|
144
143
|
if managed is not None:
|
|
145
144
|
raise RpcError("config.yaml is package-managed. Update the declarative configuration instead.",
|
|
146
145
|
condition="config_write_refused")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def write_config(path: Path, config: dict, *, profile_home: Path | None = None) -> dict:
|
|
149
|
+
if not isinstance(config, dict):
|
|
150
|
+
raise InvalidParams("config must be an object")
|
|
151
|
+
assert_config_writable(profile_home)
|
|
147
152
|
target = _write_target(path)
|
|
148
153
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
149
154
|
try:
|
|
@@ -8,10 +8,13 @@ registers its session operation here through ``remember_chat_operation``. Every
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import contextvars
|
|
12
|
+
import hashlib
|
|
11
13
|
import inspect
|
|
12
14
|
import json
|
|
13
15
|
import logging
|
|
14
16
|
import threading
|
|
17
|
+
import uuid
|
|
15
18
|
from collections import OrderedDict
|
|
16
19
|
from pathlib import Path
|
|
17
20
|
from types import SimpleNamespace
|
|
@@ -130,11 +133,46 @@ def accounts() -> dict:
|
|
|
130
133
|
client = _portal()
|
|
131
134
|
if not _authenticated(client):
|
|
132
135
|
return {"accounts": []}
|
|
136
|
+
rows = client.list_accounts()
|
|
137
|
+
try:
|
|
138
|
+
from hermes_cli.free_tier_offer import note_connected_apps_seen
|
|
139
|
+
note_connected_apps_seen([{"connected": row.get("active") is True and not row.get("disabled")} for row in rows])
|
|
140
|
+
except ImportError:
|
|
141
|
+
pass # older Agents have no connector offer
|
|
142
|
+
except Exception:
|
|
143
|
+
log.warning("connector sighting unavailable")
|
|
133
144
|
return {"accounts": [{
|
|
134
145
|
"connection_id": row["connectionId"], "connector": row["connector"], "status": row["status"],
|
|
135
146
|
"status_reason": row.get("statusReason"), "label": row["label"], "alias": row.get("alias"),
|
|
136
147
|
"active": bool(row["active"]), "disabled": bool(row.get("disabled")), "created_at": row["createdAt"],
|
|
137
|
-
} for row in
|
|
148
|
+
} for row in rows]}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def sign_in_offer(home: Path, claim_guest: str | None = None) -> dict:
|
|
152
|
+
"""Native policy and CAS only. The server owns retention of the delivered page result."""
|
|
153
|
+
result = {"available": False, "guest_id": None, "eligible": False, "due_in": None, "claimed": False}
|
|
154
|
+
try:
|
|
155
|
+
from hermes_cli import anon_auth, free_tier_offer as native
|
|
156
|
+
if not all(callable(getattr(native, name, None)) for name in ("_eligible", "offer_due_in", "claim_offer")):
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
def identity():
|
|
160
|
+
state = anon_auth.current_nous_state()
|
|
161
|
+
token = state.get("anon_token") if anon_auth.is_guest_state(state) else None
|
|
162
|
+
return hashlib.sha256(f"{home.resolve()}\0{token}".encode()).hexdigest() if isinstance(token, str) and token else None
|
|
163
|
+
|
|
164
|
+
guest = identity()
|
|
165
|
+
result["guest_id"] = guest
|
|
166
|
+
eligible = bool(native._eligible(native.CONNECTOR))
|
|
167
|
+
due = native.offer_due_in(native.CONNECTOR)
|
|
168
|
+
claimed = bool(guest and guest == claim_guest and native.claim_offer(native.CONNECTOR)) if claim_guest else False
|
|
169
|
+
if identity() != guest:
|
|
170
|
+
return {**result, "guest_id": None}
|
|
171
|
+
return {"available": True, "guest_id": guest, "eligible": eligible, "due_in": due, "claimed": claimed}
|
|
172
|
+
except ImportError:
|
|
173
|
+
return result
|
|
174
|
+
except Exception:
|
|
175
|
+
return result # retain a proven identity, never raw auth or native error details
|
|
138
176
|
|
|
139
177
|
|
|
140
178
|
def remove(connection_id: str) -> dict:
|
|
@@ -201,6 +239,14 @@ def _operation(home: Path, op_id: str):
|
|
|
201
239
|
operation = _OPS.get((str(home), op_id))
|
|
202
240
|
if operation is None:
|
|
203
241
|
raise RpcError("No connection operation with that id.", condition="operation_not_found")
|
|
242
|
+
account_id = getattr(operation, "_talaria_nous_account_id", None)
|
|
243
|
+
if account_id:
|
|
244
|
+
from . import nous_account
|
|
245
|
+
current = nous_account.read(home)
|
|
246
|
+
if not current or current["account_id"] != account_id:
|
|
247
|
+
from tools.connectors.contract import SettleReason
|
|
248
|
+
operation.settle(SettleReason.interrupt)
|
|
249
|
+
raise RpcError("No connection operation for the current account.", condition="operation_not_found")
|
|
204
250
|
return operation
|
|
205
251
|
|
|
206
252
|
|
|
@@ -226,8 +272,62 @@ def operation_view(operation) -> dict:
|
|
|
226
272
|
}
|
|
227
273
|
|
|
228
274
|
|
|
229
|
-
def
|
|
275
|
+
def _reconnect(home: Path, connector: str, account_id: str) -> dict:
|
|
276
|
+
"""Compose the existing native operation with an account-fenced, noncreating client.
|
|
277
|
+
|
|
278
|
+
Native account.find_or_start_operation always uses a creating, live-credential client
|
|
279
|
+
in its later thread. Its lifecycle primitives accept the client this result needs.
|
|
280
|
+
"""
|
|
281
|
+
from tools.connectors import account, live, managed
|
|
282
|
+
from tools.connectors.gateway.client import ConnectorClient
|
|
283
|
+
from tools.connectors.operation import ConnectionOperation, Target
|
|
284
|
+
from tools.connectors.run import drive_operation
|
|
285
|
+
from tools.connectors.contract import SettleReason
|
|
286
|
+
from . import nous_account
|
|
287
|
+
|
|
288
|
+
with account._start_lock:
|
|
289
|
+
existing = live.find_target(connector, profile_home=str(home))
|
|
290
|
+
if existing is not None:
|
|
291
|
+
if getattr(existing, "_talaria_nous_account_id", None) != account_id:
|
|
292
|
+
raise RpcError("Finish the current app connection before reconnecting this result.", condition="operation_refused")
|
|
293
|
+
_remember(home, existing)
|
|
294
|
+
return operation_view(_operation(home, existing.op_id))
|
|
295
|
+
client = ConnectorClient(initiator="desktop_page", header_provider=lambda url: nous_account.connector_headers(home, account_id, url))
|
|
296
|
+
operation = ConnectionOperation([Target(connector, "connector", "connect")], session_key=f"account:{uuid.uuid4().hex}")
|
|
297
|
+
operation._talaria_nous_account_id = account_id
|
|
298
|
+
operation._talaria_nous_client = client
|
|
299
|
+
start = account.AccountOperationStart(operation=operation, started=True)
|
|
300
|
+
context = contextvars.copy_context()
|
|
301
|
+
|
|
302
|
+
def run():
|
|
303
|
+
try:
|
|
304
|
+
drive_operation(operation, managed.managed_kind(client, "connect", force=False),
|
|
305
|
+
connection_callback=lambda payload: start.done.set(),
|
|
306
|
+
tick_seconds=managed.WATCH_TICK_SECONDS, with_urls_in_result=False)
|
|
307
|
+
except Exception:
|
|
308
|
+
start.failed = True
|
|
309
|
+
operation.settle(SettleReason.interrupt)
|
|
310
|
+
finally:
|
|
311
|
+
live.close(operation)
|
|
312
|
+
start.done.set()
|
|
313
|
+
|
|
314
|
+
live.open(operation)
|
|
315
|
+
_remember(home, operation)
|
|
316
|
+
try:
|
|
317
|
+
threading.Thread(target=lambda: context.run(run), daemon=True, name="connector-account-operation").start()
|
|
318
|
+
except Exception:
|
|
319
|
+
live.close(operation)
|
|
320
|
+
operation.settle(SettleReason.interrupt)
|
|
321
|
+
raise
|
|
322
|
+
if account.wait_for_prepare(start) and start.failed:
|
|
323
|
+
raise RpcError("Connector request failed.", condition="connectors_failed")
|
|
324
|
+
return operation_view(_operation(home, operation.op_id))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def connect(home: Path, connector: str, *, account_id: str | None = None) -> dict:
|
|
230
328
|
"""Start (or rejoin) the account-owned connect for ``connector`` and wait for its authorization link."""
|
|
329
|
+
if account_id is not None:
|
|
330
|
+
return _reconnect(home, connector, account_id)
|
|
231
331
|
from tools.connectors import account
|
|
232
332
|
from tools.connectors.contract import TargetState
|
|
233
333
|
from tools.connectors.run import reissue
|
|
@@ -236,8 +336,11 @@ def connect(home: Path, connector: str) -> dict:
|
|
|
236
336
|
page = {"initiator": "desktop_page"} if "initiator" in inspect.signature(account.find_or_start_operation).parameters else {}
|
|
237
337
|
start = account.find_or_start_operation([connector], action="connect", profile_home=str(home), **page)
|
|
238
338
|
_remember(home, start.operation)
|
|
339
|
+
_operation(home, start.operation.op_id)
|
|
239
340
|
if not start.started:
|
|
240
341
|
target = start.operation.target(connector)
|
|
342
|
+
if target is not None and target.state in (TargetState.failed, TargetState.expired) and getattr(start.operation, "_talaria_nous_client", None) is not None:
|
|
343
|
+
return respond(home, start.operation.op_id, "retry", connector, {})
|
|
241
344
|
if target is not None and target.state in (TargetState.failed, TargetState.expired) and reissue(start.operation, [connector], **page):
|
|
242
345
|
raise RpcError("This connection cannot be started again.", condition="operation_refused")
|
|
243
346
|
return operation_view(start.operation)
|
|
@@ -262,7 +365,14 @@ def respond(home: Path, op_id: str, action: str, target: str | None, env: dict)
|
|
|
262
365
|
if action == "continue":
|
|
263
366
|
apply_answer(operation, json.dumps({"settled_by": SettleReason.continue_.value}))
|
|
264
367
|
elif action == "retry" and row.kind == "connector":
|
|
265
|
-
|
|
368
|
+
client = getattr(operation, "_talaria_nous_client", None)
|
|
369
|
+
if client is not None:
|
|
370
|
+
from tools.connectors.managed import mint
|
|
371
|
+
from tools.connectors.contract import Actor, TargetState
|
|
372
|
+
if row.state not in (TargetState.failed, TargetState.expired):
|
|
373
|
+
raise RpcError("This target cannot be run again.", condition="operation_refused")
|
|
374
|
+
mint(client, operation, [row.name], reinitiate=True, actor=Actor.user)
|
|
375
|
+
elif reissue(operation, [row.name]):
|
|
266
376
|
raise RpcError("This target cannot be run again.", condition="operation_refused")
|
|
267
377
|
else:
|
|
268
378
|
status = "skipped" if action == "skip" else "approved"
|
|
@@ -288,6 +398,21 @@ def remember_chat_operation(home: Path, payload: dict) -> dict | None:
|
|
|
288
398
|
|
|
289
399
|
|
|
290
400
|
def register(registry_) -> None:
|
|
401
|
+
@registry_.method("connectors.offer.status")
|
|
402
|
+
def offer_status_(ctx: CallContext, params: dict) -> dict:
|
|
403
|
+
home = profile_home_param(params)
|
|
404
|
+
with scoped_home(home):
|
|
405
|
+
return sign_in_offer(home)
|
|
406
|
+
|
|
407
|
+
@registry_.method("connectors.offer.claim")
|
|
408
|
+
def offer_claim_(ctx: CallContext, params: dict) -> dict:
|
|
409
|
+
guest = params.get("guest_id")
|
|
410
|
+
if not isinstance(guest, str) or not guest:
|
|
411
|
+
raise InvalidParams("guest_id is required")
|
|
412
|
+
home = profile_home_param(params)
|
|
413
|
+
with scoped_home(home):
|
|
414
|
+
return sign_in_offer(home, guest)
|
|
415
|
+
|
|
291
416
|
def page(params: dict, read, *, creates_guest: bool = False):
|
|
292
417
|
with scoped_home(profile_home_param(params)):
|
|
293
418
|
_open(creates_guest=creates_guest)
|
|
@@ -346,7 +471,16 @@ def register(registry_) -> None:
|
|
|
346
471
|
def connect_(ctx: CallContext, params: dict) -> dict:
|
|
347
472
|
slug = _slug(params)
|
|
348
473
|
home = profile_home_param(params)
|
|
349
|
-
|
|
474
|
+
def begin():
|
|
475
|
+
if "nous_account_id" in params:
|
|
476
|
+
from . import nous_account
|
|
477
|
+
view = nous_account.read(home)
|
|
478
|
+
if not view or view["account_id"] != params["nous_account_id"] or slug not in view["reconnect"]:
|
|
479
|
+
raise RpcError("The signed-in account or reconnect result changed. Refresh before reconnecting.", condition="connectors_forbidden")
|
|
480
|
+
if not any(row["slug"] == slug for row in catalog()["connectors"]):
|
|
481
|
+
raise RpcError("This app is not available to reconnect.", condition="connectors_forbidden")
|
|
482
|
+
return connect(home, slug, account_id=params.get("nous_account_id"))
|
|
483
|
+
return page(params, begin, creates_guest="nous_account_id" not in params)
|
|
350
484
|
|
|
351
485
|
def gated() -> None:
|
|
352
486
|
# Reads and moves on an operation pass the availability gate too, without creating an identity.
|
|
@@ -190,6 +190,27 @@ def _plugins(home):
|
|
|
190
190
|
return out
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
def reviewed_producer(home, name, pin, sources):
|
|
194
|
+
"""Verify a store adapter's native registration, profile consent and reviewed bytes without loading it."""
|
|
195
|
+
try:
|
|
196
|
+
from hermes_cli import plugins_cmd as pc
|
|
197
|
+
metadata = pc._read_install_metadata()
|
|
198
|
+
for plugin in _plugins(home):
|
|
199
|
+
if plugin['name'] != name or not plugin['enabled'] or plugin.get('source') != 'user':
|
|
200
|
+
continue
|
|
201
|
+
root, anchor = Path(plugin['_dir']).parent, Path(plugin['_asset_anchor'])
|
|
202
|
+
record = metadata.get(root.name, {})
|
|
203
|
+
catalog = record.get('catalog', {})
|
|
204
|
+
if catalog.get('name') != name or catalog.get('sha') != pin or record.get('revision') != pin:
|
|
205
|
+
continue
|
|
206
|
+
if all((body := _read(root, file, anchor=anchor)) is not None
|
|
207
|
+
and hashlib.sha256(body).hexdigest() == digest for file, digest in sources.items()):
|
|
208
|
+
return pin
|
|
209
|
+
except (ImportError, AttributeError, OSError, ValueError, TypeError):
|
|
210
|
+
pass
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
193
214
|
def _named(home, name, *, require_panel=True):
|
|
194
215
|
return next((p for p in _plugins(home) if p['name'] == name and p['enabled'] and (p['panel'] if require_panel else p.get('_api_file'))), None)
|
|
195
216
|
|
|
@@ -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
|