@maudecode/talaria-web 1.15.0 → 1.17.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-raw.js +6 -3
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +158 -16
- package/dist/api/automation-router.js +12 -6
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +193 -6
- package/dist/api/chat-router.js +39 -16
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +12 -1
- package/dist/api/git-router.js +60 -38
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +6 -0
- package/dist/api/raw-routes.js +35 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +4 -2
- package/dist/api/router.js +8 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +974 -42
- package/dist/api/sessions-router.js +87 -25
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +254 -11
- package/dist/api/settings-router.js +68 -18
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +11 -3
- package/dist/api/sse-routes.js +145 -65
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -2
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +86 -9
- package/dist/api/tools-router.js +19 -15
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +0 -8397
- package/dist/app.js +7 -2
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +11 -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.d.ts +2 -0
- package/dist/config/agent-config.js +9 -0
- 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 +6 -3
- package/dist/http/context.js +10 -4
- 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.js +30 -7
- 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 +167 -5
- package/dist/providers/catalog.js +662 -135
- 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.d.ts +1 -0
- package/dist/redact.js +518 -15
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +79 -37
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +24 -3
- package/dist/sessions/anchor.js +94 -44
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +4 -1
- package/dist/sessions/background-tasks.js +7 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/claude-code.d.ts +19 -0
- package/dist/sessions/claude-code.js +250 -0
- package/dist/sessions/claude-code.js.map +1 -0
- package/dist/sessions/completions.d.ts +13 -1
- package/dist/sessions/completions.js +64 -12
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +14 -1
- package/dist/sessions/compress.js +52 -11
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/journal.d.ts +8 -0
- package/dist/sessions/journal.js +44 -10
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +13 -5
- package/dist/sessions/list.js +56 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +122 -9
- package/dist/sessions/merge.js +544 -33
- 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/service.d.ts +100 -4
- package/dist/sessions/service.js +567 -102
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +16 -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/state-db.d.ts +26 -4
- package/dist/sessions/state-db.js +126 -8
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.js +6 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +10 -0
- package/dist/sessions/streams.js +14 -3
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/todo.d.ts +6 -1
- package/dist/sessions/todo.js +11 -4
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +12 -1
- package/dist/sessions/tool-display.js +76 -1
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn.d.ts +49 -10
- package/dist/sessions/turn.js +459 -185
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +20 -2
- package/dist/settings.js +49 -2
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js +7 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.js +2 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/harness.js +2 -0
- package/dist/test/harness.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 +4 -1
- package/dist/tools/crons.js +58 -11
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/insights.d.ts +8 -2
- package/dist/tools/insights.js +18 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +41 -1
- package/dist/tools/kanban.js +113 -2
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp.d.ts +2 -1
- package/dist/tools/mcp.js +16 -6
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/terminal.d.ts +1 -0
- package/dist/tools/terminal.js +1 -1
- package/dist/tools/terminal.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 +15 -3
- package/dist/tools/updates.js +207 -41
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +7 -3
- package/dist/workspace/fs.js +5 -1
- package/dist/workspace/fs.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 +63 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.d.ts +28 -0
- package/dist/workspace/preview.js +110 -0
- package/dist/workspace/preview.js.map +1 -0
- package/dist/workspace/rollback.d.ts +5 -4
- package/dist/workspace/rollback.js +12 -9
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/upload.d.ts +2 -0
- package/dist/workspace/upload.js +4 -1
- package/dist/workspace/upload.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 +4 -2
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +7 -1
- package/sidecar/talaria_sidecar/home.py +98 -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 +551 -61
- package/sidecar/talaria_sidecar/methods/commands.py +61 -33
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +10 -6
- package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +60 -1
- package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
- package/sidecar/talaria_sidecar/methods/text.py +18 -21
- package/sidecar/talaria_sidecar/methods/usage.py +264 -5
- package/static/dist/FILES.txt +126 -121
- package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
- package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
- package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
- package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
- package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
- package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
- package/static/dist/assets/Select-bilaypej.js +1 -0
- package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
- package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
- package/static/dist/assets/States-nmejrwrm.js +1 -0
- package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
- package/static/dist/assets/_app-gdidzd94.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
- package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
- package/static/dist/assets/_app.insights-luywlarz.js +1 -0
- package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
- package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
- package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
- package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
- package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
- package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
- package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
- package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
- package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
- package/static/dist/assets/cancel-drd0g6d9.js +1 -0
- package/static/dist/assets/channel-cpdgf867.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
- package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
- package/static/dist/assets/copy-fw5b1pc6.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
- package/static/dist/assets/endpoints-d33siokx.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
- package/static/dist/assets/index-cxq58xb1.js +11 -0
- package/static/dist/assets/index-hgtx08n5.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
- package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
- package/static/dist/assets/link-jyxicdhg.js +1 -0
- package/static/dist/assets/login-ec21t39x.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
- package/static/dist/assets/onboarding-btnltvyz.js +1 -0
- package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
- package/static/dist/assets/refresh-b4uams1i.js +1 -0
- package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
- package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
- package/static/dist/assets/share._token-f97e94r0.js +2 -0
- package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
- package/static/dist/assets/sse-fwpqtzbg.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
- package/static/dist/assets/todoStore-eginf9h4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
- package/static/dist/assets/tts-lxdxnmjd.js +1 -0
- package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
- package/static/dist/assets/useForm-bcblv1rx.js +1 -0
- package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
- package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
- package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +2 -2
- package/static/dist/assets/AppShell-orat1s43.js +0 -1
- package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
- package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
- package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
- package/static/dist/assets/Select-fswf66up.js +0 -1
- package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
- package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
- package/static/dist/assets/States-kxyu50at.js +0 -1
- package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
- package/static/dist/assets/_app-fowyruiu.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
- package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
- package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
- package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
- package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
- package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
- package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
- package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
- package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
- package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
- package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
- package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
- package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
- package/static/dist/assets/channel-gozy3dp3.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
- package/static/dist/assets/endpoints-k8agnro8.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
- package/static/dist/assets/index-nfy0ezs6.css +0 -2
- package/static/dist/assets/index-nqed3oum.js +0 -11
- package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
- package/static/dist/assets/link-m90ahx3n.js +0 -1
- package/static/dist/assets/login-bw7m2fzo.js +0 -1
- package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
- package/static/dist/assets/onboarding-ret37j1e.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
- package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
- package/static/dist/assets/search-j5ki6a9o.js +0 -1
- package/static/dist/assets/share._token-kg8w647b.js +0 -2
- package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
- package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
- package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
- package/static/dist/assets/useForm-ocxu460f.js +0 -1
- package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
|
@@ -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))
|
|
@@ -12,7 +12,7 @@ import time
|
|
|
12
12
|
from contextlib import closing
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
-
from ..errors import InvalidParams
|
|
15
|
+
from ..errors import InvalidParams, RpcError
|
|
16
16
|
from ..home import profile_home_param, scoped_home
|
|
17
17
|
from ..rpc import CallContext
|
|
18
18
|
|
|
@@ -54,6 +54,14 @@ def drain(max_events: int) -> list[dict]:
|
|
|
54
54
|
completion_queue = getattr(registry, "completion_queue", None) if registry is not None else None
|
|
55
55
|
if completion_queue is None:
|
|
56
56
|
return []
|
|
57
|
+
try:
|
|
58
|
+
# A recovered process has no reader thread; only this probe notices its exit and queues its completion (TAL-533).
|
|
59
|
+
with registry._lock:
|
|
60
|
+
detached = [s for s in registry._running.values() if getattr(s, "detached", False)]
|
|
61
|
+
for session in detached:
|
|
62
|
+
registry._refresh_detached_session(session)
|
|
63
|
+
except Exception: # noqa: BLE001
|
|
64
|
+
log.debug("Failed to reconcile recovered processes", exc_info=True)
|
|
57
65
|
out = []
|
|
58
66
|
while len(out) < max_events:
|
|
59
67
|
try:
|
|
@@ -94,6 +102,22 @@ def mark_consumed(process_id: str) -> bool:
|
|
|
94
102
|
return False
|
|
95
103
|
|
|
96
104
|
|
|
105
|
+
def consumed_ids(process_ids: list[str]) -> list[str]:
|
|
106
|
+
"""The processes the agent already holds from its own turn: consumed via wait/log, or observed exiting via poll.
|
|
107
|
+
Checked at wakeup delivery, since the completion is usually drained before the agent's wait returns (TAL-532)."""
|
|
108
|
+
registry = _registry()
|
|
109
|
+
if registry is None:
|
|
110
|
+
return []
|
|
111
|
+
out = []
|
|
112
|
+
for process_id in process_ids:
|
|
113
|
+
try:
|
|
114
|
+
if registry.is_completion_consumed(process_id) or process_id in (getattr(registry, "_poll_observed", None) or ()):
|
|
115
|
+
out.append(process_id)
|
|
116
|
+
except Exception: # noqa: BLE001
|
|
117
|
+
log.debug("Completion consumed check failed for %r", process_id, exc_info=True)
|
|
118
|
+
return out
|
|
119
|
+
|
|
120
|
+
|
|
97
121
|
def _delivery_api():
|
|
98
122
|
"""The Agent's durable delivery ledger (``tools.async_delegation``), or None on an Agent without it."""
|
|
99
123
|
try:
|
|
@@ -387,12 +411,40 @@ def delegation_result(home: Path, session_id: str, delegation_id: str) -> str:
|
|
|
387
411
|
return format_notification(event) if event else ""
|
|
388
412
|
|
|
389
413
|
|
|
414
|
+
def recover(base_home: Path) -> int:
|
|
415
|
+
"""TAL-533: re-adopt every profile's checkpointed processes when a sidecar starts, so a process in a profile nobody
|
|
416
|
+
uses yet is watched (and its exit reported) from the start. Entering a home's scope recovers it once; a failed
|
|
417
|
+
recovery fails the call, so the server retries."""
|
|
418
|
+
from hermes_cli.profiles import _PROFILE_ID_RE
|
|
419
|
+
|
|
420
|
+
profiles_root = base_home / "profiles"
|
|
421
|
+
named = sorted(p for p in profiles_root.iterdir() if p.is_dir() and _PROFILE_ID_RE.match(p.name)) if profiles_root.is_dir() else []
|
|
422
|
+
failed = []
|
|
423
|
+
for home in [base_home, *named]:
|
|
424
|
+
try:
|
|
425
|
+
with scoped_home(home):
|
|
426
|
+
pass
|
|
427
|
+
except RpcError as exc:
|
|
428
|
+
if exc.data.get("condition") != "agent_incompatible":
|
|
429
|
+
failed.append(str(exc))
|
|
430
|
+
continue
|
|
431
|
+
# An Agent without profile isolation refuses named profiles for good; their processes stay unadopted.
|
|
432
|
+
log.warning("Background process recovery skipped for %s", home, exc_info=True)
|
|
433
|
+
if failed:
|
|
434
|
+
raise RpcError("; ".join(failed))
|
|
435
|
+
return 1 + len(named)
|
|
436
|
+
|
|
437
|
+
|
|
390
438
|
def register(registry_) -> None:
|
|
391
439
|
@registry_.method("process.drain")
|
|
392
440
|
def drain_(ctx: CallContext, params: dict) -> dict:
|
|
393
441
|
with scoped_home(profile_home_param(params)):
|
|
394
442
|
return {"events": drain(int(params.get("max_events") or 256))}
|
|
395
443
|
|
|
444
|
+
@registry_.method("process.recover")
|
|
445
|
+
def recover_(ctx: CallContext, params: dict) -> dict:
|
|
446
|
+
return {"homes": recover(profile_home_param(params, "base_home"))}
|
|
447
|
+
|
|
396
448
|
@registry_.method("process.requeue")
|
|
397
449
|
def requeue_(ctx: CallContext, params: dict) -> dict:
|
|
398
450
|
events = params.get("events")
|
|
@@ -407,6 +459,13 @@ def register(registry_) -> None:
|
|
|
407
459
|
raise InvalidParams("process_id is required")
|
|
408
460
|
return {"ok": mark_consumed(process_id)}
|
|
409
461
|
|
|
462
|
+
@registry_.method("process.consumed")
|
|
463
|
+
def consumed_(ctx: CallContext, params: dict) -> dict:
|
|
464
|
+
ids = params.get("process_ids")
|
|
465
|
+
if not isinstance(ids, list) or not all(isinstance(i, str) and i for i in ids):
|
|
466
|
+
raise InvalidParams("process_ids must be a list of process ids")
|
|
467
|
+
return {"consumed": consumed_ids(ids)}
|
|
468
|
+
|
|
410
469
|
def _delivery_params(params: dict) -> dict:
|
|
411
470
|
evt = params.get("event")
|
|
412
471
|
if not isinstance(evt, dict):
|
|
@@ -18,6 +18,7 @@ from pathlib import Path
|
|
|
18
18
|
from ..errors import InvalidParams, RpcError
|
|
19
19
|
from ..home import profile_home_param
|
|
20
20
|
from ..rpc import CallContext
|
|
21
|
+
from .config import yaml_parser
|
|
21
22
|
|
|
22
23
|
log = logging.getLogger("talaria_sidecar.profiles")
|
|
23
24
|
_PROFILE_ID_RE = re.compile(r"[a-z0-9][a-z0-9_-]{0,63}")
|
|
@@ -34,12 +35,12 @@ _TERMINAL_ENV = {
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
def _yaml_load(path: Path):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
except Exception: # noqa: BLE001
|
|
38
|
+
"""A YAML mapping, or None when the file is absent or malformed; a missing parser raises ``yaml_unavailable``."""
|
|
39
|
+
if not path.exists():
|
|
40
40
|
return None
|
|
41
|
+
parser = yaml_parser()
|
|
41
42
|
try:
|
|
42
|
-
data =
|
|
43
|
+
data = parser.safe_load(path.read_text(encoding="utf-8"))
|
|
43
44
|
except Exception: # noqa: BLE001
|
|
44
45
|
return None
|
|
45
46
|
return data if isinstance(data, dict) else None
|
|
@@ -240,7 +241,13 @@ def register(registry) -> None:
|
|
|
240
241
|
|
|
241
242
|
@registry.method("profiles.delete")
|
|
242
243
|
def delete(ctx: CallContext, params: dict) -> dict:
|
|
243
|
-
|
|
244
|
+
base_home, name = profile_home_param(params, "base_home"), str(params.get("name") or "").strip()
|
|
245
|
+
validate_name(name)
|
|
246
|
+
# Idle cached agents end their memory sessions while the profile's home still exists.
|
|
247
|
+
from .chat import release_profile_agents
|
|
248
|
+
|
|
249
|
+
release_profile_agents(base_home / "profiles" / name)
|
|
250
|
+
delete_profile(base_home, name)
|
|
244
251
|
return {"ok": True}
|
|
245
252
|
|
|
246
253
|
@registry.method("profiles.runtime_env")
|