@maudecode/talaria-web 1.14.0 → 1.16.1
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 +123 -7
- package/dist/api/automation-router.js +5 -0
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +728 -55
- package/dist/api/chat-router.js +122 -126
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.js +44 -32
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.js +5 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +6 -3
- package/dist/api/router.js +10 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +2583 -297
- package/dist/api/sessions-router.js +92 -47
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +188 -17
- package/dist/api/settings-router.js +75 -26
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +1 -1
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +13 -1
- package/dist/api/tools-router.js +10 -8
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +3620 -343
- package/dist/auth/gate.js +8 -4
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +6 -2
- package/dist/auth/oidc.js +16 -11
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +14 -2
- package/dist/auth/store.js +26 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/cli/dotenv.d.ts +1 -1
- package/dist/cli/dotenv.js +36 -3
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.js +20 -15
- package/dist/config/agent-config.js.map +1 -1
- package/dist/http/addresses.d.ts +6 -0
- package/dist/http/addresses.js +26 -0
- package/dist/http/addresses.js.map +1 -1
- package/dist/http/context.d.ts +7 -5
- package/dist/http/context.js.map +1 -1
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.d.ts +1 -1
- package/dist/onboarding.js +28 -8
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.js +1 -1
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +85 -1
- package/dist/providers/catalog.js +280 -43
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +9 -4
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.js +493 -13
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +104 -42
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +22 -3
- package/dist/sessions/anchor.js +87 -42
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +140 -0
- package/dist/sessions/background-tasks.js +352 -0
- package/dist/sessions/background-tasks.js.map +1 -0
- package/dist/sessions/background-updates.d.ts +56 -0
- package/dist/sessions/background-updates.js +152 -0
- package/dist/sessions/background-updates.js.map +1 -0
- package/dist/sessions/cli-sessions.d.ts +6 -0
- package/dist/sessions/cli-sessions.js +20 -7
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +17 -1
- package/dist/sessions/completions.js +84 -13
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +33 -0
- package/dist/sessions/compress.js +66 -0
- package/dist/sessions/compress.js.map +1 -0
- package/dist/sessions/list.d.ts +22 -4
- package/dist/sessions/list.js +113 -33
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +81 -7
- package/dist/sessions/merge.js +391 -26
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +5 -0
- package/dist/sessions/pending.js +30 -10
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/relay.d.ts +7 -1
- package/dist/sessions/relay.js +17 -4
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +118 -6
- package/dist/sessions/service.js +564 -65
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +22 -1
- package/dist/sessions/session.js +29 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.js +11 -2
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.d.ts +22 -0
- package/dist/sessions/source-kind.js +54 -0
- package/dist/sessions/source-kind.js.map +1 -0
- package/dist/sessions/state-db.d.ts +10 -1
- package/dist/sessions/state-db.js +48 -17
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -0
- package/dist/sessions/store.js +24 -5
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +3 -0
- package/dist/sessions/streams.js +0 -5
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +4 -0
- package/dist/sessions/titles.js +16 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +1 -0
- package/dist/sessions/turn-context.js +3 -0
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +85 -7
- package/dist/sessions/turn.js +518 -170
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +13 -0
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.js +2 -0
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/tools/commands.d.ts +15 -0
- package/dist/tools/commands.js +52 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/crons.d.ts +38 -4
- package/dist/tools/crons.js +323 -36
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/mcp.js +5 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +1 -1
- package/dist/tools/update-notifications.js +1 -1
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +8 -3
- package/dist/tools/updates.js +133 -34
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/git.d.ts +57 -10
- package/dist/workspace/git.js +302 -195
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media-refs.d.ts +13 -0
- package/dist/workspace/media-refs.js +165 -0
- package/dist/workspace/media-refs.js.map +1 -0
- package/dist/workspace/media.d.ts +19 -0
- package/dist/workspace/media.js +61 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +7 -1
- package/dist/workspace/workspaces.js +30 -21
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -4
- package/dist/workspace/worktrees.js +28 -33
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/scripts/replay_sidecar.py +3 -1
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +59 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +19 -3
- package/sidecar/talaria_sidecar/methods/chat.py +360 -27
- package/sidecar/talaria_sidecar/methods/commands.py +46 -31
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +22 -14
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +318 -0
- package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
- package/sidecar/talaria_sidecar/methods/text.py +1 -20
- package/sidecar/talaria_sidecar/methods/usage.py +7 -1
- package/static/dist/FILES.txt +124 -119
- package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
- package/static/dist/assets/Button-nx37f4u1.js +1 -0
- package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
- package/static/dist/assets/Dialog-mec42hvb.js +1 -0
- package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
- package/static/dist/assets/{Markdown-emii55o0.js → Markdown-jqrx5es0.js} +125 -125
- package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
- package/static/dist/assets/Select-h4ivl9qf.js +1 -0
- package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
- package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
- package/static/dist/assets/States-ehsb6lzo.js +1 -0
- package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
- package/static/dist/assets/_app-m8d961ef.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
- package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
- package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
- package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
- package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
- package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
- package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-m6nx9g8t.js → _app.session._sessionId-hh27wwu2.js} +1 -1
- package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
- package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
- package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
- package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
- package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
- package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-kejgxpil.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
- package/static/dist/assets/back-eu64w89w.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
- package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
- package/static/dist/assets/channel-mbvmf2vv.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-erassib3.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
- package/static/dist/assets/{copied-gpchexqu.js → copied-h65fc3cx.js} +1 -1
- package/static/dist/assets/copy-e6977uwq.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
- package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-geqx7zgl.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
- package/static/dist/assets/{index-f9bzglkn.js → index-h00usru2.js} +3 -3
- package/static/dist/assets/index-n73ijkv2.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
- package/static/dist/assets/{line-laaa8h99.js → line-dmzlfe0u.js} +1 -1
- package/static/dist/assets/link-budeg5t9.js +1 -0
- package/static/dist/assets/login-kjhasvp8.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-ix4junux.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
- package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-gpv06065.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-jgroj2ew.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
- package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-kbcje1uy.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
- package/static/dist/assets/{saved-brdfjjnw.js → saved-bazmcup9.js} +1 -1
- package/static/dist/assets/search-jen74z4u.js +1 -0
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
- package/static/dist/assets/share._token-dan9hgvj.js +2 -0
- package/static/dist/assets/{sliders-horizontal-gcgscv8r.js → sliders-horizontal-f9cb3lh3.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-olvknr62.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
- package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
- package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-htelhc68.js} +1 -1
- package/static/dist/assets/useForm-h2t0u14e.js +1 -0
- package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
- package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
- package/static/dist/assets/{useParams-katwvzil.js → useParams-dqd64jv7.js} +1 -1
- package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-j30pvsy7.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-haqi9qzu.js +0 -1
- package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
- package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
- package/static/dist/assets/RightPanel-o73y98dn.js +0 -1
- package/static/dist/assets/Select-jld3yeni.js +0 -1
- package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
- package/static/dist/assets/SettingsLayout-lnn2wo2s.js +0 -1
- package/static/dist/assets/States-e5hv6gdh.js +0 -1
- package/static/dist/assets/Toaster-c4jq1i26.js +0 -1
- package/static/dist/assets/_app-bnip2pjg.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
- package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
- package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
- package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
- package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
- package/static/dist/assets/_app.memory-i00paisv.js +0 -1
- package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
- package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
- package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
- package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
- package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
- package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
- package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-hqnf84fc.js +0 -36
- package/static/dist/assets/channel-o77ioktg.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
- package/static/dist/assets/endpoints-e58saih3.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
- package/static/dist/assets/index-gkh7kum4.css +0 -2
- package/static/dist/assets/infoDiagram-27XIBGKW-oo7kdyte.js +0 -2
- package/static/dist/assets/lazyRouteComponent-b1h2ykmb.js +0 -1
- package/static/dist/assets/link-d8f0788e.js +0 -1
- package/static/dist/assets/login-llkohrcg.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
- package/static/dist/assets/onboarding-lx0d6b38.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
- package/static/dist/assets/search-gt2ueyvg.js +0 -1
- package/static/dist/assets/share._token-oyjv046r.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
- package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
- package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
- package/static/dist/assets/useForm-kd759sjk.js +0 -1
- package/static/dist/assets/useLocale-hds4xmja.js +0 -1
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
Agent's per-model reasoning capability.
|
|
3
3
|
|
|
4
4
|
The TypeScript server owns every config.yaml *policy* (which keys mean what);
|
|
5
|
-
the sidecar only parses and serialises YAML with the Agent's
|
|
6
|
-
side needs no YAML dependency. Writes are atomic and keep the file's mode.
|
|
5
|
+
the sidecar only parses and serialises YAML with the Agent's own parser so the
|
|
6
|
+
Node side needs no YAML dependency. Writes are atomic and keep the file's mode.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import importlib
|
|
11
12
|
import logging
|
|
12
13
|
import os
|
|
13
14
|
import tempfile
|
|
@@ -20,12 +21,15 @@ from ..rpc import CallContext
|
|
|
20
21
|
log = logging.getLogger("talaria_sidecar.config")
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
def yaml_parser():
|
|
25
|
+
"""The Agent's YAML module: ``hermes_yaml`` on Agents that ship it, PyYAML on released Agents."""
|
|
26
|
+
failures = []
|
|
27
|
+
for name in ("hermes_yaml", "yaml"):
|
|
28
|
+
try:
|
|
29
|
+
return importlib.import_module(name)
|
|
30
|
+
except Exception as exc: # noqa: BLE001
|
|
31
|
+
failures.append(f"{name}: {exc}")
|
|
32
|
+
raise RpcError(f"No YAML parser available ({'; '.join(failures)})", condition="yaml_unavailable")
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
def config_path_param(params: dict) -> Path:
|
|
@@ -45,7 +49,7 @@ def read_config(path: Path) -> dict:
|
|
|
45
49
|
if not path.exists():
|
|
46
50
|
return {"path": str(path), "exists": False, "config": {}}
|
|
47
51
|
try:
|
|
48
|
-
data =
|
|
52
|
+
data = yaml_parser().safe_load(path.read_text(encoding="utf-8"))
|
|
49
53
|
except RpcError:
|
|
50
54
|
raise
|
|
51
55
|
except Exception as exc: # noqa: BLE001
|
|
@@ -70,7 +74,7 @@ def write_config(path: Path, config: dict) -> dict:
|
|
|
70
74
|
raise InvalidParams("config must be an object")
|
|
71
75
|
target = _write_target(path)
|
|
72
76
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
-
text =
|
|
77
|
+
text = yaml_parser().safe_dump(config, sort_keys=False, allow_unicode=True)
|
|
74
78
|
mode = None
|
|
75
79
|
try:
|
|
76
80
|
mode = target.stat().st_mode & 0o777
|
|
@@ -379,25 +379,33 @@ def register(registry) -> None:
|
|
|
379
379
|
post["profile"] = str(body["profile"]).strip()
|
|
380
380
|
if body.get("toast_notifications") is False:
|
|
381
381
|
post["toast_notifications"] = False
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if post.get("profile")
|
|
382
|
+
if body.get("owner_profile"):
|
|
383
|
+
post["owner_profile"] = str(body["owner_profile"]).strip()
|
|
384
|
+
execution_home = params.get("execution_home")
|
|
385
|
+
if post.get("profile"):
|
|
386
|
+
if not execution_home:
|
|
387
|
+
raise InvalidParams("execution_home is required for a profile job")
|
|
388
|
+
home = Path(execution_home)
|
|
389
|
+
model, provider = (value.strip() or None if isinstance(value, str) else None for value in (body.get("model"), body.get("provider")))
|
|
390
|
+
# The scheduler ignores Talaria's profile field. Pin the execution profile's main model
|
|
391
|
+
# before creating it in that profile's own store. Scheduled workers bind credentials/config
|
|
392
|
+
# to the store home; owner_profile only preserves management from the creating Web profile.
|
|
393
|
+
if post.get("profile") and execution_home and not (model and provider) and not body.get("no_agent"):
|
|
386
394
|
try:
|
|
387
|
-
from cron.jobs import
|
|
388
|
-
|
|
389
|
-
with _SNAPSHOT_LOCK, scoped_home(Path(
|
|
390
|
-
|
|
391
|
-
if
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
from cron.jobs import _main_model_pin
|
|
396
|
+
|
|
397
|
+
with _SNAPSHOT_LOCK, scoped_home(Path(execution_home)):
|
|
398
|
+
profile_provider, profile_model = _main_model_pin()
|
|
399
|
+
if not profile_model:
|
|
400
|
+
raise ValueError("Profile has no main model configured")
|
|
401
|
+
provider = provider or profile_provider
|
|
402
|
+
model = model or profile_model
|
|
395
403
|
except Exception as exc: # noqa: BLE001
|
|
396
|
-
raise RpcError(f"Cannot safely resolve cron
|
|
404
|
+
raise RpcError(f"Cannot safely resolve cron model for profile {post['profile']!r}", condition="cron_snapshot_failed") from exc
|
|
397
405
|
with _store(home), scoped_home(home):
|
|
398
406
|
try:
|
|
399
407
|
job = jobs.create_job(prompt=body.get("prompt") or "", schedule=body["schedule"], name=body.get("name") or None, deliver=body.get("deliver") or "local",
|
|
400
|
-
skills=body.get("skills") or [], model=
|
|
408
|
+
skills=body.get("skills") or [], model=model, provider=provider, **kwargs)
|
|
401
409
|
except Exception as exc: # noqa: BLE001
|
|
402
410
|
raise InvalidParams(str(exc)) from exc
|
|
403
411
|
if post:
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""``oauth.*``: the Agent's device-code sign-ins for Nous Portal, OpenAI Codex, xAI and MiniMax (TAL-398).
|
|
2
|
+
|
|
3
|
+
``oauth.start`` asks the provider for a code and answers what the user has to see; a daemon thread then waits for the
|
|
4
|
+
approval and saves the credential with the Agent's own auth-store helpers under the flow's profile home, the steps its
|
|
5
|
+
dashboard and ``hermes auth add`` run. Only the lifecycle is ours: a flow belongs to the home that started it, a cancel
|
|
6
|
+
or an expiry means nothing is saved afterwards, and a new start for the same home and provider supersedes the old one.
|
|
7
|
+
Flows live in this process only.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import secrets
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable
|
|
21
|
+
|
|
22
|
+
from ..errors import InvalidParams, RpcError
|
|
23
|
+
from ..home import profile_home_param, scoped_home
|
|
24
|
+
from ..rpc import CallContext
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger("talaria_sidecar.oauth")
|
|
27
|
+
|
|
28
|
+
_CODEX_ISSUER = "https://auth.openai.com"
|
|
29
|
+
_CODEX_EXPIRES_IN = 15 * 60 # OpenAI's device-code lifetime, as the Agent dashboard uses it
|
|
30
|
+
# An ended flow stays readable this long so the last poll still sees its ending.
|
|
31
|
+
_ENDED_TTL = 10 * 60
|
|
32
|
+
# A pending flow past its code's expiry by this much ends expired even if its provider call has not returned yet.
|
|
33
|
+
_EXPIRY_GRACE = 60
|
|
34
|
+
|
|
35
|
+
_DENIED = "Sign-in was declined."
|
|
36
|
+
_EXPIRED = "The sign-in code expired before it was approved."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _Stop(Exception):
|
|
40
|
+
"""Raised inside a cancelled flow's provider call so the worker unwinds without saving."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Flow:
|
|
45
|
+
flow_id: str
|
|
46
|
+
provider: str
|
|
47
|
+
home: Path
|
|
48
|
+
expires_at: float
|
|
49
|
+
interval: int
|
|
50
|
+
status: str = "pending"
|
|
51
|
+
error: str | None = None
|
|
52
|
+
ended_at: float | None = None
|
|
53
|
+
# The OAuth ``error`` code of the provider's last non-200 answer (``access_denied``, ``expired_token``).
|
|
54
|
+
oauth_error: str = ""
|
|
55
|
+
cancelled: threading.Event = field(default_factory=threading.Event)
|
|
56
|
+
# Serialises the cancel check with the credential save: a cancel cannot land between them.
|
|
57
|
+
lock: threading.Lock = field(default_factory=threading.Lock)
|
|
58
|
+
|
|
59
|
+
def end(self, status: str, error: str | None = None) -> None:
|
|
60
|
+
self.status, self.error, self.ended_at = status, error, time.time()
|
|
61
|
+
|
|
62
|
+
def view(self) -> dict:
|
|
63
|
+
return {"flow_id": self.flow_id, "provider": self.provider, "status": self.status, "error": self.error}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _Watched:
|
|
67
|
+
"""The HTTP client handed to an Agent poll helper: stops a cancelled flow and records the OAuth error code."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, client: Any, flow: Flow):
|
|
70
|
+
self._client, self._flow = client, flow
|
|
71
|
+
|
|
72
|
+
def _guard(self) -> None:
|
|
73
|
+
if self._flow.cancelled.is_set():
|
|
74
|
+
raise _Stop()
|
|
75
|
+
|
|
76
|
+
def post(self, *args: Any, **kwargs: Any) -> Any:
|
|
77
|
+
self._guard()
|
|
78
|
+
response = self._client.post(*args, **kwargs)
|
|
79
|
+
if response.status_code != 200:
|
|
80
|
+
try:
|
|
81
|
+
payload = response.json()
|
|
82
|
+
except Exception: # noqa: BLE001 - a non-JSON error carries no OAuth code
|
|
83
|
+
payload = None
|
|
84
|
+
if isinstance(payload, dict) and isinstance(payload.get("error"), str):
|
|
85
|
+
self._flow.oauth_error = payload["error"]
|
|
86
|
+
return response
|
|
87
|
+
|
|
88
|
+
def send(self, *args: Any, **kwargs: Any) -> Any:
|
|
89
|
+
self._guard()
|
|
90
|
+
return self._client.send(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
def __getattr__(self, name: str) -> Any:
|
|
93
|
+
return getattr(self._client, name)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _first_line(exc: BaseException) -> str:
|
|
97
|
+
text = (str(exc).strip().splitlines() or [""])[0].strip() or type(exc).__name__
|
|
98
|
+
return text[:300]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _remaining(flow: Flow) -> int:
|
|
102
|
+
return max(1, int(flow.expires_at - time.time()))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _now_z() -> str:
|
|
106
|
+
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _client(timeout: float, **kwargs: Any) -> Any:
|
|
110
|
+
import httpx
|
|
111
|
+
|
|
112
|
+
return httpx.Client(timeout=httpx.Timeout(timeout), headers={"Accept": "application/json"}, **kwargs)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ── providers: (begin, wait, save) over the Agent's own steps ──────────
|
|
116
|
+
# begin() -> (display, state) runs under the flow's home; wait(flow, state) -> tokens polls; save(flow, state, tokens)
|
|
117
|
+
# runs under the home with the flow's lock held.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _nous_begin() -> tuple[dict, dict]:
|
|
121
|
+
from hermes_cli import anon_auth
|
|
122
|
+
from hermes_cli.auth import PROVIDER_REGISTRY, _request_device_code
|
|
123
|
+
|
|
124
|
+
if anon_auth.guest_enabled() and anon_auth.has_guest():
|
|
125
|
+
# The free tier's sign-in moves its connectors to the account (``anon_auth.run_sign_in``); a plain device-code
|
|
126
|
+
# sign-in here would orphan them.
|
|
127
|
+
raise RpcError("This profile uses the Nous free tier. Sign in from Hermes with `hermes portal`.", condition="oauth_unsupported")
|
|
128
|
+
pconfig = PROVIDER_REGISTRY["nous"]
|
|
129
|
+
portal = (os.getenv("HERMES_PORTAL_BASE_URL") or os.getenv("NOUS_PORTAL_BASE_URL") or pconfig.portal_base_url).rstrip("/")
|
|
130
|
+
with _client(15.0) as client:
|
|
131
|
+
data = _request_device_code(client, portal, pconfig.client_id, pconfig.scope)
|
|
132
|
+
display = {"user_code": str(data["user_code"]), "verification_url": str(data["verification_uri_complete"]), "expires_in": int(data["expires_in"]), "interval": int(data["interval"])}
|
|
133
|
+
return display, {"portal": portal, "client_id": pconfig.client_id, "scope": pconfig.scope, "device_code": str(data["device_code"])}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _nous_wait(flow: Flow, state: dict) -> dict:
|
|
137
|
+
from hermes_cli.auth import _poll_for_token
|
|
138
|
+
|
|
139
|
+
with _client(15.0) as client:
|
|
140
|
+
return _poll_for_token(client=_Watched(client, flow), portal_base_url=state["portal"], client_id=state["client_id"], device_code=state["device_code"],
|
|
141
|
+
expires_in=_remaining(flow), poll_interval=flow.interval)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _nous_save(flow: Flow, state: dict, token_data: dict) -> None:
|
|
145
|
+
from hermes_cli import anon_auth
|
|
146
|
+
from hermes_cli.auth import persist_nous_credentials, refresh_nous_oauth_from_state
|
|
147
|
+
|
|
148
|
+
now = datetime.now(timezone.utc)
|
|
149
|
+
ttl = int(token_data.get("expires_in") or 0)
|
|
150
|
+
auth_state = {
|
|
151
|
+
"portal_base_url": state["portal"], "inference_base_url": token_data.get("inference_base_url"), "client_id": state["client_id"],
|
|
152
|
+
"scope": token_data.get("scope") or state["scope"], "token_type": token_data.get("token_type", "Bearer"),
|
|
153
|
+
"access_token": token_data["access_token"], "refresh_token": token_data.get("refresh_token"), "obtained_at": now.isoformat(),
|
|
154
|
+
"expires_at": datetime.fromtimestamp(now.timestamp() + ttl, tz=timezone.utc).isoformat() if ttl else None, "expires_in": ttl,
|
|
155
|
+
}
|
|
156
|
+
full_state = refresh_nous_oauth_from_state(auth_state, timeout_seconds=15.0, force_refresh=False)
|
|
157
|
+
persist_nous_credentials(full_state)
|
|
158
|
+
anon_auth.settle_after_upgrade(full_state)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _codex_begin() -> tuple[dict, dict]:
|
|
162
|
+
from hermes_cli.auth import CODEX_OAUTH_CLIENT_ID
|
|
163
|
+
from hermes_cli.auth_codex import _codex_request_device_code
|
|
164
|
+
|
|
165
|
+
data = _codex_request_device_code(_CODEX_ISSUER, CODEX_OAUTH_CLIENT_ID)
|
|
166
|
+
display = {"user_code": str(data["user_code"]), "verification_url": f"{_CODEX_ISSUER}/codex/device", "expires_in": _CODEX_EXPIRES_IN, "interval": int(data["interval"])}
|
|
167
|
+
return display, {"device_auth_id": str(data["device_auth_id"]), "user_code": str(data["user_code"])}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _codex_wait(flow: Flow, state: dict) -> dict:
|
|
171
|
+
"""The Agent's Codex poll (``auth_codex._codex_poll_authorization_code``) cannot be stopped, so its loop runs here:
|
|
172
|
+
403/404 is still pending, a transport error is retried a few times in a row, and the exchange is the Agent's."""
|
|
173
|
+
import httpx
|
|
174
|
+
from hermes_cli.auth import CODEX_OAUTH_CLIENT_ID
|
|
175
|
+
from hermes_cli.auth_codex import _codex_exchange_authorization_code, _codex_http_client
|
|
176
|
+
|
|
177
|
+
payload = {"device_auth_id": state["device_auth_id"], "user_code": state["user_code"]}
|
|
178
|
+
blips = 0
|
|
179
|
+
code_resp = None
|
|
180
|
+
with _codex_http_client(timeout=httpx.Timeout(15.0)) as client:
|
|
181
|
+
while code_resp is None:
|
|
182
|
+
if flow.cancelled.wait(flow.interval):
|
|
183
|
+
raise _Stop()
|
|
184
|
+
if time.time() >= flow.expires_at:
|
|
185
|
+
raise TimeoutError(_EXPIRED)
|
|
186
|
+
try:
|
|
187
|
+
response = client.post(f"{_CODEX_ISSUER}/api/accounts/deviceauth/token", json=payload, headers={"Content-Type": "application/json"})
|
|
188
|
+
except httpx.TransportError:
|
|
189
|
+
blips += 1
|
|
190
|
+
if blips >= 6:
|
|
191
|
+
raise
|
|
192
|
+
continue
|
|
193
|
+
blips = 0
|
|
194
|
+
if response.status_code == 200:
|
|
195
|
+
code_resp = response.json()
|
|
196
|
+
elif response.status_code not in (403, 404):
|
|
197
|
+
raise RuntimeError(f"OpenAI device sign-in returned HTTP {response.status_code}.")
|
|
198
|
+
if flow.cancelled.is_set():
|
|
199
|
+
raise _Stop()
|
|
200
|
+
tokens = _codex_exchange_authorization_code(_CODEX_ISSUER, CODEX_OAUTH_CLIENT_ID, code_resp)
|
|
201
|
+
return {"access_token": tokens.get("access_token", ""), "refresh_token": tokens.get("refresh_token", "")}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _codex_save(flow: Flow, state: dict, tokens: dict) -> None:
|
|
205
|
+
from hermes_cli.auth import _save_codex_tokens
|
|
206
|
+
|
|
207
|
+
_save_codex_tokens(tokens, _now_z())
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _xai_begin() -> tuple[dict, dict]:
|
|
211
|
+
from hermes_cli.auth import _xai_oauth_discovery, _xai_oauth_request_device_code
|
|
212
|
+
|
|
213
|
+
discovery = _xai_oauth_discovery(20.0)
|
|
214
|
+
with _client(20.0) as client:
|
|
215
|
+
data = _xai_oauth_request_device_code(client)
|
|
216
|
+
display = {"user_code": str(data["user_code"]), "verification_url": str(data.get("verification_uri_complete") or data["verification_uri"]),
|
|
217
|
+
"expires_in": int(data["expires_in"]), "interval": int(data["interval"])}
|
|
218
|
+
return display, {"discovery": discovery, "device_code": str(data["device_code"])}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _xai_wait(flow: Flow, state: dict) -> dict:
|
|
222
|
+
from hermes_cli.auth import _xai_oauth_poll_device_token
|
|
223
|
+
|
|
224
|
+
with _client(20.0) as client:
|
|
225
|
+
data = _xai_oauth_poll_device_token(_Watched(client, flow), token_endpoint=state["discovery"]["token_endpoint"], device_code=state["device_code"],
|
|
226
|
+
expires_in=_remaining(flow), poll_interval=flow.interval)
|
|
227
|
+
return {
|
|
228
|
+
"access_token": str(data.get("access_token", "") or "").strip(), "refresh_token": str(data.get("refresh_token", "") or "").strip(),
|
|
229
|
+
"id_token": str(data.get("id_token", "") or "").strip(), "expires_in": data.get("expires_in"),
|
|
230
|
+
"token_type": str(data.get("token_type") or "Bearer").strip() or "Bearer",
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _xai_save(flow: Flow, state: dict, tokens: dict) -> None:
|
|
235
|
+
from hermes_cli.auth import _save_xai_oauth_tokens, mark_provider_active_if_unset, unsuppress_credential_source
|
|
236
|
+
|
|
237
|
+
# As the Agent dashboard does: persist without taking over an active chat provider, become active only when none
|
|
238
|
+
# is, and re-enable the device-code source an earlier removal suppressed.
|
|
239
|
+
_save_xai_oauth_tokens(tokens, discovery=state["discovery"], auth_mode="oauth_device_code", set_active=False, last_refresh=_now_z())
|
|
240
|
+
mark_provider_active_if_unset("xai-oauth")
|
|
241
|
+
unsuppress_credential_source("xai-oauth", "device_code")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _minimax_begin() -> tuple[dict, dict]:
|
|
245
|
+
from hermes_cli.auth import MINIMAX_OAUTH_CLIENT_ID, MINIMAX_OAUTH_GLOBAL_BASE, _minimax_pkce_pair, _minimax_request_user_code
|
|
246
|
+
|
|
247
|
+
verifier, challenge, csrf_state = _minimax_pkce_pair()
|
|
248
|
+
portal = (os.getenv("MINIMAX_PORTAL_BASE_URL") or MINIMAX_OAUTH_GLOBAL_BASE).rstrip("/")
|
|
249
|
+
with _client(15.0, follow_redirects=True) as client:
|
|
250
|
+
data = _minimax_request_user_code(client, portal_base_url=portal, client_id=MINIMAX_OAUTH_CLIENT_ID, code_challenge=challenge, state=csrf_state)
|
|
251
|
+
interval_ms = int(data["interval"]) if data.get("interval") is not None else None
|
|
252
|
+
# ``expired_in`` is a unix-ms deadline or a TTL in seconds (the Agent's own heuristic).
|
|
253
|
+
expired_in = int(data["expired_in"])
|
|
254
|
+
expires_in = max(0, int(expired_in / 1000.0 - time.time())) if expired_in > 1_000_000_000_000 else expired_in
|
|
255
|
+
display = {"user_code": str(data["user_code"]), "verification_url": str(data["verification_uri"]), "expires_in": expires_in, "interval": max(2, (interval_ms or 2000) // 1000)}
|
|
256
|
+
return display, {"portal": portal, "client_id": MINIMAX_OAUTH_CLIENT_ID, "verifier": verifier, "user_code": str(data["user_code"]), "expired_in": expired_in, "interval_ms": interval_ms}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _minimax_wait(flow: Flow, state: dict) -> dict:
|
|
260
|
+
from hermes_cli.auth import _minimax_poll_token
|
|
261
|
+
|
|
262
|
+
with _client(15.0, follow_redirects=True) as client:
|
|
263
|
+
return _minimax_poll_token(_Watched(client, flow), portal_base_url=state["portal"], client_id=state["client_id"], user_code=state["user_code"],
|
|
264
|
+
code_verifier=state["verifier"], expired_in=state["expired_in"], interval_ms=state["interval_ms"])
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _minimax_save(flow: Flow, state: dict, token_data: dict) -> None:
|
|
268
|
+
from hermes_cli.auth import MINIMAX_OAUTH_GLOBAL_INFERENCE, MINIMAX_OAUTH_SCOPE, _minimax_resolve_token_expiry_unix, _minimax_save_auth_state
|
|
269
|
+
|
|
270
|
+
now = datetime.now(timezone.utc)
|
|
271
|
+
expires_at = _minimax_resolve_token_expiry_unix(int(token_data["expired_in"]), now=now)
|
|
272
|
+
_minimax_save_auth_state({
|
|
273
|
+
"provider": "minimax-oauth", "region": "global", "portal_base_url": state["portal"], "inference_base_url": MINIMAX_OAUTH_GLOBAL_INFERENCE,
|
|
274
|
+
"client_id": state["client_id"], "scope": MINIMAX_OAUTH_SCOPE, "token_type": token_data.get("token_type", "Bearer"),
|
|
275
|
+
"access_token": token_data["access_token"], "refresh_token": token_data["refresh_token"], "resource_url": token_data.get("resource_url"),
|
|
276
|
+
"obtained_at": now.isoformat(), "expires_at": datetime.fromtimestamp(expires_at, tz=timezone.utc).isoformat(), "expires_in": max(0, int(expires_at - now.timestamp())),
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
Provider = tuple[Callable[[], tuple[dict, dict]], Callable[[Flow, dict], dict], Callable[[Flow, dict, dict], None]]
|
|
281
|
+
PROVIDERS: dict[str, Provider] = {
|
|
282
|
+
"nous": (_nous_begin, _nous_wait, _nous_save),
|
|
283
|
+
"openai-codex": (_codex_begin, _codex_wait, _codex_save),
|
|
284
|
+
"xai-oauth": (_xai_begin, _xai_wait, _xai_save),
|
|
285
|
+
"minimax-oauth": (_minimax_begin, _minimax_wait, _minimax_save),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_FLOWS: dict[str, Flow] = {}
|
|
289
|
+
_FLOWS_LOCK = threading.Lock()
|
|
290
|
+
# The newest start per (home, provider): only it may publish a flow, whatever order the provider requests finish in.
|
|
291
|
+
_GENERATIONS: dict[tuple[str, str], int] = {}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _ending(flow: Flow, exc: BaseException) -> tuple[str, str]:
|
|
295
|
+
"""Classify why a provider call ended without tokens: declined, expired, or another failure with its reason."""
|
|
296
|
+
code = str(getattr(exc, "code", None) or getattr(exc, "oauth_error_code", None) or "")
|
|
297
|
+
if flow.oauth_error == "access_denied" or code in ("access_denied", "authorization_denied"):
|
|
298
|
+
return "denied", _DENIED
|
|
299
|
+
if flow.oauth_error == "expired_token" or code in ("expired_token", "device_code_timeout", "timeout") or isinstance(exc, TimeoutError) or time.time() >= flow.expires_at:
|
|
300
|
+
return "expired", _EXPIRED
|
|
301
|
+
return "error", _first_line(exc)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _run(flow: Flow, wait: Callable[[Flow, dict], dict], save: Callable[[Flow, dict, dict], None], state: dict) -> None:
|
|
305
|
+
try:
|
|
306
|
+
tokens = wait(flow, state)
|
|
307
|
+
with flow.lock:
|
|
308
|
+
if flow.status != "pending": # cancelled or expired while the provider answered: nothing is saved
|
|
309
|
+
return
|
|
310
|
+
with scoped_home(flow.home):
|
|
311
|
+
save(flow, state, tokens)
|
|
312
|
+
flow.end("approved")
|
|
313
|
+
log.info("oauth: %s sign-in approved (flow=%s)", flow.provider, flow.flow_id[:6])
|
|
314
|
+
except _Stop:
|
|
315
|
+
pass
|
|
316
|
+
except Exception as exc: # noqa: BLE001 - the thread has no caller; the ending is the flow's status
|
|
317
|
+
with flow.lock:
|
|
318
|
+
if flow.status == "pending":
|
|
319
|
+
flow.end(*_ending(flow, exc))
|
|
320
|
+
log.info("oauth: %s sign-in ended %s (flow=%s)", flow.provider, flow.status, flow.flow_id[:6])
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _cancel(flow: Flow) -> None:
|
|
324
|
+
with flow.lock:
|
|
325
|
+
if flow.status == "pending":
|
|
326
|
+
flow.cancelled.set()
|
|
327
|
+
flow.end("cancelled")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _home_key(home: Path) -> str:
|
|
331
|
+
try:
|
|
332
|
+
return str(home.resolve())
|
|
333
|
+
except OSError:
|
|
334
|
+
return str(home)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _same_home(left: Path, right: Path) -> bool:
|
|
338
|
+
try:
|
|
339
|
+
return left.resolve() == right.resolve()
|
|
340
|
+
except OSError:
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _gc(now: float) -> None:
|
|
345
|
+
with _FLOWS_LOCK:
|
|
346
|
+
for flow_id, flow in list(_FLOWS.items()):
|
|
347
|
+
if flow.ended_at is not None and now - flow.ended_at > _ENDED_TTL:
|
|
348
|
+
del _FLOWS[flow_id]
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _pending(home: Path, provider: str) -> list[Flow]:
|
|
352
|
+
with _FLOWS_LOCK:
|
|
353
|
+
return [f for f in _FLOWS.values() if f.provider == provider and f.status == "pending" and _same_home(f.home, home)]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def start(home: Path, provider: str, *, providers: dict[str, Provider] | None = None) -> dict:
|
|
357
|
+
steps = (providers or PROVIDERS).get(provider)
|
|
358
|
+
if steps is None:
|
|
359
|
+
raise InvalidParams(f"{provider} has no device-code sign-in")
|
|
360
|
+
begin, wait, save = steps
|
|
361
|
+
_gc(time.time())
|
|
362
|
+
key = (_home_key(home), provider)
|
|
363
|
+
with _FLOWS_LOCK:
|
|
364
|
+
generation = _GENERATIONS[key] = _GENERATIONS.get(key, 0) + 1
|
|
365
|
+
# A new start replaces the pending flow for this home and provider before the code request, so an approval of the
|
|
366
|
+
# old code that lands while the provider answers can no longer save.
|
|
367
|
+
for old in _pending(home, provider):
|
|
368
|
+
_cancel(old)
|
|
369
|
+
try:
|
|
370
|
+
with scoped_home(home):
|
|
371
|
+
display, state = begin()
|
|
372
|
+
except RpcError:
|
|
373
|
+
raise
|
|
374
|
+
except Exception as exc: # noqa: BLE001
|
|
375
|
+
log.debug("oauth: %s start failed", provider, exc_info=True)
|
|
376
|
+
raise RpcError(_first_line(exc), condition="oauth_failed") from exc
|
|
377
|
+
if not str(display["verification_url"]).lower().startswith(("https://", "http://")):
|
|
378
|
+
# The browser opens this link: anything but a web address is refused before a flow exists.
|
|
379
|
+
raise RpcError("The provider answered an invalid sign-in link.", condition="oauth_failed")
|
|
380
|
+
flow = Flow(secrets.token_urlsafe(16), provider, home, time.time() + int(display["expires_in"]), max(1, int(display["interval"])))
|
|
381
|
+
with _FLOWS_LOCK:
|
|
382
|
+
if _GENERATIONS.get(key) != generation:
|
|
383
|
+
# A newer start for this home and provider began while this one waited on the provider: it owns the sign-in.
|
|
384
|
+
raise RpcError("A newer sign-in for this provider replaced this one.", condition="oauth_superseded")
|
|
385
|
+
superseded = [f for f in _FLOWS.values() if f.provider == provider and f.status == "pending" and _same_home(f.home, home)]
|
|
386
|
+
_FLOWS[flow.flow_id] = flow
|
|
387
|
+
for old in superseded:
|
|
388
|
+
_cancel(old)
|
|
389
|
+
threading.Thread(target=_run, args=(flow, wait, save, state), daemon=True, name=f"oauth-{flow.flow_id[:6]}").start()
|
|
390
|
+
return {"flow_id": flow.flow_id, "provider": provider, "status": "pending", **display}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _flow(home: Path, flow_id: str) -> Flow:
|
|
394
|
+
with _FLOWS_LOCK:
|
|
395
|
+
flow = _FLOWS.get(flow_id)
|
|
396
|
+
if flow is None or not _same_home(flow.home, home):
|
|
397
|
+
raise RpcError("Unknown or expired sign-in.", condition="oauth_flow_not_found")
|
|
398
|
+
return flow
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def poll(home: Path, flow_id: str) -> dict:
|
|
402
|
+
flow = _flow(home, flow_id)
|
|
403
|
+
if flow.status == "pending" and time.time() > flow.expires_at + _EXPIRY_GRACE:
|
|
404
|
+
# The provider call outlived its code: end it now, and a late approval can no longer save.
|
|
405
|
+
with flow.lock:
|
|
406
|
+
if flow.status == "pending":
|
|
407
|
+
flow.cancelled.set()
|
|
408
|
+
flow.end("expired", _EXPIRED)
|
|
409
|
+
return flow.view()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def cancel(home: Path, flow_id: str) -> dict:
|
|
413
|
+
flow = _flow(home, flow_id)
|
|
414
|
+
_cancel(flow)
|
|
415
|
+
return flow.view()
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _flow_id(params: dict) -> str:
|
|
419
|
+
flow_id = str(params.get("flow_id") or "").strip()
|
|
420
|
+
if not flow_id:
|
|
421
|
+
raise InvalidParams("flow_id is required")
|
|
422
|
+
return flow_id
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def register(reg) -> None:
|
|
426
|
+
@reg.method("oauth.start")
|
|
427
|
+
def start_(ctx: CallContext, params: dict) -> dict:
|
|
428
|
+
provider = str(params.get("provider") or "").strip().lower()
|
|
429
|
+
if not provider:
|
|
430
|
+
raise InvalidParams("provider is required")
|
|
431
|
+
return start(profile_home_param(params), provider)
|
|
432
|
+
|
|
433
|
+
@reg.method("oauth.poll", requires_agent=False)
|
|
434
|
+
def poll_(ctx: CallContext, params: dict) -> dict:
|
|
435
|
+
return poll(profile_home_param(params), _flow_id(params))
|
|
436
|
+
|
|
437
|
+
@reg.method("oauth.cancel", requires_agent=False)
|
|
438
|
+
def cancel_(ctx: CallContext, params: dict) -> dict:
|
|
439
|
+
return cancel(profile_home_param(params), _flow_id(params))
|