@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
|
@@ -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):
|
|
@@ -27,9 +27,14 @@ def _agent():
|
|
|
27
27
|
return None
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def _refusal(entry, pc, native):
|
|
30
|
+
def _refusal(entry, pc, native, *, removed_entries=None):
|
|
31
31
|
try:
|
|
32
|
-
|
|
32
|
+
if removed_entries is None:
|
|
33
|
+
native.raise_if_removed(entry.name, entry.repo)
|
|
34
|
+
else:
|
|
35
|
+
from hermes_cli.plugin_catalog import match_removed
|
|
36
|
+
if any(match_removed(value, removed_entries) for value in (entry.name, entry.repo)):
|
|
37
|
+
return 'This plugin was removed from the catalog.'
|
|
33
38
|
native._refuse_unsupported_catalog_platform(entry)
|
|
34
39
|
from hermes_cli.plugins_manifest import requires_hermes_error
|
|
35
40
|
reason = requires_hermes_error({'requires_hermes': entry.requires_hermes})
|
|
@@ -196,3 +201,69 @@ def install(name, consent_token=None):
|
|
|
196
201
|
del _receipts[next(iter(_receipts))]
|
|
197
202
|
_receipts[token] = {'identity': identity, 'preview': preview, 'expires': time.monotonic() + 300}
|
|
198
203
|
return {'name': name, 'status': 'needs_consent', 'findings': findings, 'consent_token': token}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def reviewed_importer(home):
|
|
207
|
+
"""Load only attested native structured APIs, sharing catalog identity and enablement policy."""
|
|
208
|
+
import sys
|
|
209
|
+
import types
|
|
210
|
+
from . import dashboard_plugins
|
|
211
|
+
reviewed = json.loads((Path(__file__).parents[2] / 'agent_importer.json').read_text())
|
|
212
|
+
pin, hashes = reviewed['revision'], reviewed['files']
|
|
213
|
+
# Older Agent config imports initialize files. Check the pure in-tree catalog before importing their command owner.
|
|
214
|
+
try:
|
|
215
|
+
from hermes_cli import plugin_catalog as source
|
|
216
|
+
entry = next((e for e in source.load_catalog() if e.name == 'agent-importers'), None)
|
|
217
|
+
if entry is None or entry.sha != pin:
|
|
218
|
+
return None
|
|
219
|
+
except (ImportError, AttributeError, OSError, ValueError):
|
|
220
|
+
return None
|
|
221
|
+
agent = _agent()
|
|
222
|
+
if agent is None or not dashboard_plugins._supported():
|
|
223
|
+
return None
|
|
224
|
+
source, pc, native = agent
|
|
225
|
+
try:
|
|
226
|
+
from pm.workspace import enabled_plugin_dirs
|
|
227
|
+
from hermes_cli.web_server_dashboard import _plugin_enable_sets_here
|
|
228
|
+
enabled, disabled = _plugin_enable_sets_here()
|
|
229
|
+
if not (Path(home) / 'plugins').is_dir():
|
|
230
|
+
return None
|
|
231
|
+
metadata = pc._read_install_metadata()
|
|
232
|
+
matches = [(key, record) for key, record in metadata.items() if isinstance(record, dict)
|
|
233
|
+
and isinstance(record.get('catalog'), dict) and record['catalog'].get('name') == 'agent-importers']
|
|
234
|
+
if len(matches) != 1:
|
|
235
|
+
return None
|
|
236
|
+
key, record = matches[0]
|
|
237
|
+
root = pc._sanitize_plugin_name(key, Path(home) / 'plugins')
|
|
238
|
+
# Admission must not fetch or populate the profile's live catalog cache on a preview.
|
|
239
|
+
cached = dashboard_plugins._read(Path(home), 'cache/plugin-catalog.json', 4 * 1024 * 1024, anchor=Path(home))
|
|
240
|
+
if cached is None and ((Path(home) / 'cache/plugin-catalog.json').exists() or (Path(home) / 'cache/plugin-catalog.json').is_symlink()):
|
|
241
|
+
return None
|
|
242
|
+
cached = json.loads(cached) if cached is not None else {}
|
|
243
|
+
if not isinstance(cached, dict) or not isinstance(cached.get('entries', []), list):
|
|
244
|
+
return None
|
|
245
|
+
if any(e.get('name') == 'agent-importers' and e.get('sha') != pin for e in cached.get('entries', []) if isinstance(e, dict)):
|
|
246
|
+
return None
|
|
247
|
+
removed = source.load_removed_list() + source._removed_from_list(cached.get('removed'))
|
|
248
|
+
if (entry is None or entry.sha != pin or _refusal(entry, pc, native, removed_entries=removed)
|
|
249
|
+
or record.get('revision') != pin or record['catalog'].get('sha') != pin
|
|
250
|
+
or record['catalog'].get('repo') != entry.repo
|
|
251
|
+
or key not in enabled or key in disabled or root.resolve() not in {p.resolve() for p in enabled_plugin_dirs()}):
|
|
252
|
+
return None
|
|
253
|
+
bodies = {name: dashboard_plugins._read(root, name, anchor=Path(home)) for name in hashes}
|
|
254
|
+
if any(body is None or hashlib.sha256(body).hexdigest() != hashes[name] for name, body in bodies.items()):
|
|
255
|
+
return None
|
|
256
|
+
# Compile verified bytes, never re-open a mutable plugin path or execute its entry point.
|
|
257
|
+
package = '_talaria_reviewed_agent_importers'
|
|
258
|
+
if package not in sys.modules:
|
|
259
|
+
module = types.ModuleType(package)
|
|
260
|
+
module.__path__ = []
|
|
261
|
+
sys.modules[package] = module
|
|
262
|
+
for name in ('importer', 'sync'):
|
|
263
|
+
module = types.ModuleType(f'{package}.{name}')
|
|
264
|
+
module.__package__ = package
|
|
265
|
+
sys.modules[module.__name__] = module
|
|
266
|
+
exec(compile(bodies[name + '.py'], f'<reviewed-agent-importers/{name}.py>', 'exec'), module.__dict__)
|
|
267
|
+
return sys.modules[package + '.importer'].AgentImporter, sys.modules[package + '.sync'], [pin, str(root), _tree_digest(root)]
|
|
268
|
+
except (ImportError, AttributeError, OSError, ValueError, TypeError, RpcError):
|
|
269
|
+
return None
|
|
@@ -228,6 +228,18 @@ def _unavailable_plugin(provider_id: str) -> bool:
|
|
|
228
228
|
return not any(p is profile for _name, p in installed_plugin_profiles())
|
|
229
229
|
|
|
230
230
|
|
|
231
|
+
def require_runtime_ownership(requested: str | None) -> None:
|
|
232
|
+
"""A process-wide Agent registry must not lend another home's plugin to this runtime."""
|
|
233
|
+
if not requested:
|
|
234
|
+
return
|
|
235
|
+
try:
|
|
236
|
+
if not _unavailable_plugin(requested.strip().lower()):
|
|
237
|
+
return
|
|
238
|
+
except Exception: # noqa: BLE001 - an unverified plugin owner cannot authorize execution
|
|
239
|
+
log.debug("runtime plugin ownership check failed", exc_info=True)
|
|
240
|
+
raise RpcError("Requested provider is unavailable in this profile", condition="credential_missing")
|
|
241
|
+
|
|
242
|
+
|
|
231
243
|
def _agent_knows_provider(name: str) -> bool:
|
|
232
244
|
"""Whether the Agent's auth layer can resolve this provider id at all.
|
|
233
245
|
|
|
@@ -323,6 +335,35 @@ def model_ids(provider_id: str, *, force_refresh: bool) -> tuple[list[str], bool
|
|
|
323
335
|
return [], False
|
|
324
336
|
|
|
325
337
|
|
|
338
|
+
def model_allowance_snapshot(provider_id: str, *, force_refresh: bool) -> dict:
|
|
339
|
+
"""Native picker metadata in this call's scoped home; no billing write or second usage poll."""
|
|
340
|
+
if provider_id != "nous":
|
|
341
|
+
return {}
|
|
342
|
+
try:
|
|
343
|
+
from hermes_cli.inventory import build_model_options_payload, load_picker_context
|
|
344
|
+
from hermes_cli.model_picker_usage import nous_picker_usage
|
|
345
|
+
except ImportError:
|
|
346
|
+
return {"metadata_status": "unsupported"}
|
|
347
|
+
try:
|
|
348
|
+
payload = build_model_options_payload(load_picker_context(), refresh=force_refresh)
|
|
349
|
+
row = next((r for r in payload.get("providers", []) if r.get("slug") == provider_id), {})
|
|
350
|
+
pending = row.get("pricing_pending") is True or row.get("free_tier_pending") is True
|
|
351
|
+
plan = row.get("plan_usage")
|
|
352
|
+
fresh = nous_picker_usage() if isinstance(plan, dict) else None
|
|
353
|
+
return {
|
|
354
|
+
"metadata_status": "pending" if pending else "stale" if plan and fresh is None else "current",
|
|
355
|
+
"plan_usage": plan if isinstance(plan, dict) else None,
|
|
356
|
+
"usage_levels": {mid: level for mid, level in (row.get("usage_levels") or {}).items()
|
|
357
|
+
if isinstance(mid, str) and level in ("free", "low", "medium", "top_tier")},
|
|
358
|
+
"unavailable_model_ids": [mid for mid in row.get("unavailable_models", []) if isinstance(mid, str)] if not pending else [],
|
|
359
|
+
"allowance_pending": pending,
|
|
360
|
+
"free_tier": row.get("free_tier") if isinstance(row.get("free_tier"), bool) else None,
|
|
361
|
+
}
|
|
362
|
+
except Exception: # noqa: BLE001 - unknown entitlement is not unavailability
|
|
363
|
+
log.debug("native picker allowance unavailable", exc_info=True)
|
|
364
|
+
return {"metadata_status": "error"}
|
|
365
|
+
|
|
366
|
+
|
|
326
367
|
def _requests_local_runtime(requested: str | None) -> bool:
|
|
327
368
|
"""Whether a resolution asks for the Agent's managed llama.cpp provider, picked the way the Agent picks it: the
|
|
328
369
|
named provider, else the profile's, else ``HERMES_INFERENCE_PROVIDER``."""
|
|
@@ -367,6 +408,7 @@ def local_models() -> dict:
|
|
|
367
408
|
|
|
368
409
|
|
|
369
410
|
def resolve_runtime(*, requested: str | None, api_key: str | None, base_url: str | None, target_model: str | None) -> dict:
|
|
411
|
+
require_runtime_ownership(requested)
|
|
370
412
|
try:
|
|
371
413
|
from hermes_cli.runtime_provider import format_runtime_provider_error, resolve_runtime_provider
|
|
372
414
|
except Exception as exc: # noqa: BLE001
|
|
@@ -535,6 +577,13 @@ def register(reg) -> None:
|
|
|
535
577
|
with scoped_home(profile_home_param(params)):
|
|
536
578
|
return {"providers": registry()}
|
|
537
579
|
|
|
580
|
+
@reg.method("providers.nous_account")
|
|
581
|
+
def nous_account_(ctx: CallContext, params: dict) -> dict:
|
|
582
|
+
from . import nous_account
|
|
583
|
+
home = profile_home_param(params)
|
|
584
|
+
with scoped_home(home):
|
|
585
|
+
return {"nous_account": nous_account.read(home)}
|
|
586
|
+
|
|
538
587
|
@reg.method("providers.auth_status")
|
|
539
588
|
def auth(ctx: CallContext, params: dict) -> dict:
|
|
540
589
|
with scoped_home(profile_home_param(params)):
|
|
@@ -547,7 +596,8 @@ def register(reg) -> None:
|
|
|
547
596
|
raise InvalidParams("provider is required")
|
|
548
597
|
with scoped_home(profile_home_param(params)):
|
|
549
598
|
ids, fallback = model_ids(provider, force_refresh=bool(params.get("force_refresh", False)))
|
|
550
|
-
return {"provider": provider, "model_ids": ids, "fallback": fallback
|
|
599
|
+
return {"provider": provider, "model_ids": ids, "fallback": fallback,
|
|
600
|
+
**model_allowance_snapshot(provider, force_refresh=bool(params.get("force_refresh", False)))}
|
|
551
601
|
|
|
552
602
|
@reg.method("providers.local_models")
|
|
553
603
|
def local(ctx: CallContext, params: dict) -> dict:
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Read the reviewed Hermetic store without executing its API or migrating legacy data."""
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
import difflib
|
|
5
|
+
import hashlib
|
|
6
|
+
import itertools
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import stat
|
|
12
|
+
|
|
13
|
+
from ..errors import InvalidParams
|
|
14
|
+
from . import dashboard_plugins
|
|
15
|
+
|
|
16
|
+
_PIN = 'f7026c949d8831b26910f2ebd3434e8c58cdff89'
|
|
17
|
+
_SOURCES = {
|
|
18
|
+
'__init__.py': '9dfb815be185965bf06c7c5a0fec401f6f376569d1a6830497502898fffcf01d',
|
|
19
|
+
'plugin.yaml': 'b4fbd03f70f50d2cc391e530b3e4079ecbe249ab6e5c2e4702d475911327b50e',
|
|
20
|
+
'dashboard/manifest.json': '466cada1eb3345f6fa7e12a5eaef44f8a19bb02546b9456fa0576d81ff89b6c1',
|
|
21
|
+
'dashboard/plugin_api.py': '23f1c612559f0086ff5c7da6d3cc8fe0c596a05c6f6eaa89db94c8c4d8c99d1d',
|
|
22
|
+
}
|
|
23
|
+
_NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9._ -]{0,127}')
|
|
24
|
+
_FILE = re.compile(r'([a-f0-9]{16})-SKILL[.]md')
|
|
25
|
+
_MAX_TEXT = 32 * 1024
|
|
26
|
+
_MAX_REVISIONS = 64
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def _directory(anchor, parts=()):
|
|
31
|
+
"""Hold every traversal no-follow, including the profile's ancestors, through the read."""
|
|
32
|
+
anchor = Path(anchor).absolute()
|
|
33
|
+
components = (*anchor.parts[1:], *parts)
|
|
34
|
+
if any(p in ('', '.', '..') or '/' in p or '\\' in p for p in components):
|
|
35
|
+
raise ValueError('Invalid history path')
|
|
36
|
+
fd = os.open(anchor.anchor, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
37
|
+
try:
|
|
38
|
+
for part in components:
|
|
39
|
+
child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
|
|
40
|
+
os.close(fd)
|
|
41
|
+
fd = child
|
|
42
|
+
yield fd
|
|
43
|
+
finally:
|
|
44
|
+
os.close(fd)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _read(fd, name, limit):
|
|
48
|
+
child = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=fd)
|
|
49
|
+
with os.fdopen(child, 'rb') as file:
|
|
50
|
+
info = os.fstat(file.fileno())
|
|
51
|
+
if not stat.S_ISREG(info.st_mode) or info.st_size > limit:
|
|
52
|
+
raise ValueError('History file is too large or not regular')
|
|
53
|
+
data = file.read(limit + 1)
|
|
54
|
+
if len(data) > limit:
|
|
55
|
+
raise ValueError('History file is too large')
|
|
56
|
+
return data, info.st_mtime
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _timestamp(value):
|
|
60
|
+
try:
|
|
61
|
+
date = datetime.fromtimestamp(value, timezone.utc) if type(value) in (int, float) else datetime.fromisoformat(value.replace('Z', '+00:00'))
|
|
62
|
+
return date.astimezone(timezone.utc).isoformat() if date.tzinfo else None
|
|
63
|
+
except (TypeError, ValueError, OverflowError, OSError, AttributeError):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _result(state, reason=None, **fields):
|
|
68
|
+
return dict(state=state, reason=reason, backend_revision=None, revisions=[], selected=None,
|
|
69
|
+
comparison=None, replay_available=False) | fields
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _identity(catalog, name):
|
|
73
|
+
entries = catalog(skip_disabled=True, include_hidden=True)
|
|
74
|
+
matches = [entry for entry in entries if entry.get('load_name') == name]
|
|
75
|
+
if len(matches) != 1:
|
|
76
|
+
return None
|
|
77
|
+
selected = matches[0]
|
|
78
|
+
declared = selected.get('name')
|
|
79
|
+
# Hermetic keys storage by declared name; two catalog copies cannot be attributed safely.
|
|
80
|
+
if not isinstance(declared, str) or not _NAME.fullmatch(declared) or '..' in declared:
|
|
81
|
+
return None
|
|
82
|
+
return selected if len([entry for entry in entries if entry.get('name') == declared]) == 1 else None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def inspect_revisions(home, name, revision=None, compare_to=None):
|
|
86
|
+
from .skills import _skill_helper
|
|
87
|
+
|
|
88
|
+
if not isinstance(name, str) or not 0 < len(name) <= 256:
|
|
89
|
+
raise InvalidParams('Invalid skill identity')
|
|
90
|
+
if any(v is not None and (not isinstance(v, str) or not re.fullmatch('[a-f0-9]{64}', v)) for v in (revision, compare_to)):
|
|
91
|
+
raise InvalidParams('Invalid revision identity')
|
|
92
|
+
if not getattr(os, 'O_NOFOLLOW', 0) or not dashboard_plugins.reviewed_producer(home, 'hermetic', _PIN, _SOURCES):
|
|
93
|
+
return _result('unavailable', 'backend_unavailable')
|
|
94
|
+
try:
|
|
95
|
+
catalog = _skill_helper('skill_catalog')
|
|
96
|
+
selected = _identity(catalog, name) if catalog else None
|
|
97
|
+
if selected is None:
|
|
98
|
+
return _result('unavailable', 'identity_unavailable')
|
|
99
|
+
declared = selected['name']
|
|
100
|
+
local = Path(selected['path']).relative_to(home / 'skills')
|
|
101
|
+
if local.name != 'SKILL.md':
|
|
102
|
+
raise ValueError('Not a local skill')
|
|
103
|
+
with _directory(home, ('skills', *local.parts[:-1])) as fd:
|
|
104
|
+
current, _ = _read(fd, 'SKILL.md', _MAX_TEXT)
|
|
105
|
+
provenance = {}
|
|
106
|
+
with _directory(home, ('skills',)) as fd:
|
|
107
|
+
try:
|
|
108
|
+
ledger, _ = _read(fd, '.curator_ledger.jsonl', 256 * 1024)
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
ledger = b''
|
|
111
|
+
for line in ledger.decode('utf-8').splitlines():
|
|
112
|
+
row = json.loads(line)
|
|
113
|
+
if not isinstance(row, dict):
|
|
114
|
+
raise ValueError('Invalid ledger')
|
|
115
|
+
if row.get('skill') == declared:
|
|
116
|
+
after = row.get('after') or []
|
|
117
|
+
if not isinstance(after, list):
|
|
118
|
+
raise ValueError('Invalid ledger provenance')
|
|
119
|
+
for item in after:
|
|
120
|
+
if isinstance(item, dict) and isinstance(item.get('sha256'), str):
|
|
121
|
+
actor = row.get('actor')
|
|
122
|
+
actor = actor if isinstance(actor, str) and len(actor) <= 80 and not any(ord(c) < 32 for c in actor) else None
|
|
123
|
+
provenance[item['sha256']] = (_timestamp(row.get('ts')), actor)
|
|
124
|
+
try:
|
|
125
|
+
with _directory(home, ('hermetic', 'skill-versions', declared)) as fd:
|
|
126
|
+
with os.scandir(fd) as iterator:
|
|
127
|
+
files = list(itertools.islice(iterator, _MAX_REVISIONS + 1))
|
|
128
|
+
if len(files) > _MAX_REVISIONS:
|
|
129
|
+
raise ValueError('Too many revisions')
|
|
130
|
+
rows = []
|
|
131
|
+
for file in files:
|
|
132
|
+
match = _FILE.fullmatch(file.name)
|
|
133
|
+
if not match:
|
|
134
|
+
continue
|
|
135
|
+
data, modified = _read(fd, file.name, _MAX_TEXT)
|
|
136
|
+
sha = hashlib.sha256(data).hexdigest()
|
|
137
|
+
if not sha.startswith(match[1]):
|
|
138
|
+
raise ValueError('Revision hash does not match saved bytes')
|
|
139
|
+
text = data.decode('utf-8')
|
|
140
|
+
if len(text.splitlines()) > 512:
|
|
141
|
+
raise ValueError('Too many revision lines')
|
|
142
|
+
saved_at, actor = provenance.get(sha, (_timestamp(modified), None))
|
|
143
|
+
identity = hashlib.sha256(json.dumps([str(home), name, sha]).encode()).hexdigest()
|
|
144
|
+
rows.append(dict(id=identity, sha256=sha, label=sha[:12], saved_at=saved_at, actor=actor,
|
|
145
|
+
current=sha == hashlib.sha256(current).hexdigest(), content=text))
|
|
146
|
+
except FileNotFoundError:
|
|
147
|
+
rows = []
|
|
148
|
+
if not rows:
|
|
149
|
+
if revision or compare_to:
|
|
150
|
+
raise InvalidParams('Revision no longer exists; reload history')
|
|
151
|
+
return _result('missing', 'history_unavailable', backend_revision=_PIN)
|
|
152
|
+
rows.sort(key=lambda row: (row['saved_at'] or '', row['sha256']), reverse=True)
|
|
153
|
+
by_id = {row['id']: row for row in rows}
|
|
154
|
+
target = by_id.get(revision) if revision else rows[0]
|
|
155
|
+
if target is None:
|
|
156
|
+
raise InvalidParams('Revision no longer exists; reload history')
|
|
157
|
+
source = by_id.get(compare_to) if compare_to else next((r for r in rows if r['id'] != target['id']), None)
|
|
158
|
+
if compare_to and source is None:
|
|
159
|
+
raise InvalidParams('Revision no longer exists; reload history')
|
|
160
|
+
comparison = None
|
|
161
|
+
if source:
|
|
162
|
+
lines = difflib.unified_diff(source['content'].splitlines(keepends=True), target['content'].splitlines(keepends=True),
|
|
163
|
+
fromfile=source['label'], tofile=target['label'])
|
|
164
|
+
diff = ''.join(line if line.endswith('\n') else line + '\n\\n' for line in lines)
|
|
165
|
+
comparison = dict(from_revision=source['id'], to_revision=target['id'], diff=diff)
|
|
166
|
+
# Native admission can be revoked while filesystem reads are in progress.
|
|
167
|
+
if not dashboard_plugins.reviewed_producer(home, 'hermetic', _PIN, _SOURCES):
|
|
168
|
+
return _result('unavailable', 'backend_unavailable')
|
|
169
|
+
if _identity(catalog, name) != selected:
|
|
170
|
+
return _result('unavailable', 'identity_unavailable')
|
|
171
|
+
return dict(state='available', reason=None, backend_revision=_PIN,
|
|
172
|
+
revisions=[{k: v for k, v in row.items() if k != 'content'} for row in rows],
|
|
173
|
+
selected=dict(revision=target['id'], content=target['content']), comparison=comparison, replay_available=False)
|
|
174
|
+
except InvalidParams:
|
|
175
|
+
raise
|
|
176
|
+
except (ImportError, AttributeError, OSError, ValueError, TypeError, KeyError):
|
|
177
|
+
return _result('unavailable', 'invalid_history')
|