@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
|
@@ -11,14 +11,18 @@ through ``approval.respond`` / ``clarify.respond``; ``rpc.cancel`` or
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import contextlib
|
|
14
|
+
import contextvars
|
|
15
|
+
import copy
|
|
14
16
|
import hashlib
|
|
15
17
|
import inspect
|
|
16
18
|
import json
|
|
17
19
|
import logging
|
|
20
|
+
import os
|
|
18
21
|
import threading
|
|
19
22
|
import time
|
|
20
23
|
import uuid
|
|
21
24
|
from collections import OrderedDict
|
|
25
|
+
from pathlib import Path
|
|
22
26
|
from typing import Any
|
|
23
27
|
|
|
24
28
|
from ..errors import InvalidParams, RpcError
|
|
@@ -28,6 +32,10 @@ from ..rpc import CallContext
|
|
|
28
32
|
log = logging.getLogger("talaria_sidecar.chat")
|
|
29
33
|
|
|
30
34
|
_AGENT_CACHE_MAX = 32
|
|
35
|
+
# Shutdown waits this long for evicted agents' memory providers; the server kills the sidecar 5 s after closing stdin.
|
|
36
|
+
_AGENT_RELEASE_TIMEOUT = 4.0
|
|
37
|
+
# Profile deletion waits this long for the profile's memory flushes, under the server's 120 s call timeout.
|
|
38
|
+
_PROFILE_RELEASE_TIMEOUT = 60.0
|
|
31
39
|
_TOOL_RESULT_SNIPPET_MAX = 4000
|
|
32
40
|
# Predecessor ``_TOOL_ARG_CONTENT_KEYS`` (#4928): card content / diff-reconstruction inputs keep the long cap.
|
|
33
41
|
_TOOL_ARG_CONTENT_KEYS = frozenset({"command", "cmd", "script", "code", "patch", "diff", "old_string", "new_string", "content", "path", "file_path"})
|
|
@@ -64,8 +72,16 @@ _RUNS_BY_SESSION: dict[str, str] = {}
|
|
|
64
72
|
_APPROVAL_CB_OWNER: dict[str, str] = {}
|
|
65
73
|
_APPROVAL_CB_LOCK = threading.Lock()
|
|
66
74
|
_RUNS_LOCK = threading.Lock()
|
|
67
|
-
|
|
75
|
+
# session id -> (agent, signature, profile home).
|
|
76
|
+
_AGENT_CACHE: "OrderedDict[str, tuple[Any, str, Any]]" = OrderedDict()
|
|
68
77
|
_AGENT_CACHE_LOCK = threading.Lock()
|
|
78
|
+
# Running release thread -> the profile home it runs under.
|
|
79
|
+
_RELEASES: dict[threading.Thread, Any] = {}
|
|
80
|
+
_RELEASES_LOCK = threading.Lock()
|
|
81
|
+
# Agents inside ``chat.commit_memory`` (guarded by ``_AGENT_CACHE_LOCK``): like a turn, a commit defers their release.
|
|
82
|
+
_COMMITTING: list = []
|
|
83
|
+
# Set (under ``_RUNS_LOCK``) when shutdown drains: no turn is admitted after the drain's snapshot of ``_RUNS``.
|
|
84
|
+
_DRAINING = threading.Event()
|
|
69
85
|
|
|
70
86
|
|
|
71
87
|
def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
|
|
@@ -87,6 +103,16 @@ _RAW_RESULT_MAX_KEYS = 64
|
|
|
87
103
|
_RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success")
|
|
88
104
|
|
|
89
105
|
|
|
106
|
+
def _result_diff(raw: Any) -> str | None:
|
|
107
|
+
"""A dict (or JSON-object text) result's string ``diff`` field, whole; the server decides what it shows."""
|
|
108
|
+
try:
|
|
109
|
+
data = raw if isinstance(raw, dict) else json.loads(str(raw or ""))
|
|
110
|
+
except Exception: # noqa: BLE001
|
|
111
|
+
return None
|
|
112
|
+
diff = data.get("diff") if isinstance(data, dict) else None
|
|
113
|
+
return diff if isinstance(diff, str) else None
|
|
114
|
+
|
|
115
|
+
|
|
90
116
|
def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
|
|
91
117
|
"""The tool result as the server's outcome rule reads it, bounded: a dict (or JSON-object text) keeps its first
|
|
92
118
|
``_RAW_RESULT_MAX_KEYS`` top-level fields plus its outcome fields, scalars as they are, text and non-empty nested
|
|
@@ -231,6 +257,98 @@ def _context_length(agent: Any) -> dict:
|
|
|
231
257
|
return out
|
|
232
258
|
|
|
233
259
|
|
|
260
|
+
# TAL-539: the hard cap on raw tool output kept in a compressed context (predecessor ``_hard_prune_post_compression_*``).
|
|
261
|
+
_TAIL_TOOL_BUDGET_TOKENS = 4096
|
|
262
|
+
_TAIL_TOOL_MIN_SNIPPET_TOKENS = 256
|
|
263
|
+
_ROUGH_TOKEN_CHARS = 4
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _content_text(content: Any) -> str:
|
|
267
|
+
if isinstance(content, list):
|
|
268
|
+
return " ".join(str(p.get("text") or "") for p in content if isinstance(p, dict))
|
|
269
|
+
return str(content or "")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _rough_tokens(text: str) -> int:
|
|
273
|
+
"""The Agent's estimate, which counts CJK and other non-ASCII text as denser; ceil(chars / 4) without it."""
|
|
274
|
+
try:
|
|
275
|
+
from agent.model_metadata import estimate_tokens_rough
|
|
276
|
+
|
|
277
|
+
return int(estimate_tokens_rough(text))
|
|
278
|
+
except Exception: # noqa: BLE001
|
|
279
|
+
return -(-len(text) // _ROUGH_TOKEN_CHARS)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _capped_tool_result(text: str, tokens: int, keep_tokens: int) -> str:
|
|
283
|
+
"""The kept head of an over-budget tool result plus a note on what the model no longer sees."""
|
|
284
|
+
prefix = "[Talaria context budget: omitted "
|
|
285
|
+
suffix = f" (~{tokens} rough tokens) from this tool result; the full output remains in the visible transcript.]"
|
|
286
|
+
if keep_tokens < _TAIL_TOOL_MIN_SNIPPET_TOKENS:
|
|
287
|
+
return f"{prefix}{len(text)} chars{suffix}"
|
|
288
|
+
room = keep_tokens - _rough_tokens(f"{prefix}{len(text)} of {len(text)} chars{suffix}") - 1
|
|
289
|
+
snippet = text[: len(text) * room // tokens]
|
|
290
|
+
# Density varies along the text: shrink until the kept head fits the remaining tokens.
|
|
291
|
+
while snippet and _rough_tokens(snippet) > room:
|
|
292
|
+
snippet = snippet[: len(snippet) * 3 // 4]
|
|
293
|
+
snippet = snippet.rstrip()
|
|
294
|
+
if not snippet:
|
|
295
|
+
return f"{prefix}{len(text)} chars{suffix}"
|
|
296
|
+
return f"{snippet}\n\n{prefix}{len(text) - len(snippet)} of {len(text)} chars{suffix}"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _cap_tool_results(messages: list, compressor: Any) -> list:
|
|
300
|
+
"""Newest tool results spend one shared budget; the first one over it is cut to the remainder, older ones to a note."""
|
|
301
|
+
budget = getattr(compressor, "tail_token_budget", None)
|
|
302
|
+
budget = budget if isinstance(budget, int) and budget > 0 else _TAIL_TOOL_BUDGET_TOKENS
|
|
303
|
+
threshold = getattr(compressor, "threshold_tokens", None)
|
|
304
|
+
if isinstance(threshold, int) and threshold > 0:
|
|
305
|
+
budget = min(budget, max(512, threshold // 2))
|
|
306
|
+
budget = max(512, budget)
|
|
307
|
+
spent = 0
|
|
308
|
+
out = list(messages)
|
|
309
|
+
for i in range(len(out) - 1, -1, -1):
|
|
310
|
+
msg = out[i]
|
|
311
|
+
if msg.get("role") != "tool":
|
|
312
|
+
continue
|
|
313
|
+
text = _content_text(msg.get("content"))
|
|
314
|
+
if not text.strip():
|
|
315
|
+
continue
|
|
316
|
+
tokens = _rough_tokens(text)
|
|
317
|
+
if tokens <= budget - spent:
|
|
318
|
+
spent += tokens
|
|
319
|
+
continue
|
|
320
|
+
out[i] = {**msg, "content": _capped_tool_result(text, tokens, budget - spent)}
|
|
321
|
+
spent = budget
|
|
322
|
+
return out
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _post_compression_context(agent: Any, messages: list, system_message: Any) -> tuple[list, int | None]:
|
|
326
|
+
"""TAL-539: tool output produced after a mid-turn compression never went through the compressor's pruning. Run that
|
|
327
|
+
pruning plus a hard cap on the model context, and estimate the next request from it (predecessor
|
|
328
|
+
``_prune_context_tool_results_after_compression`` / ``_estimate_post_compression_context_tokens``)."""
|
|
329
|
+
compressor = getattr(agent, "context_compressor", None)
|
|
330
|
+
context = messages
|
|
331
|
+
prune = getattr(compressor, "_prune_old_tool_results", None)
|
|
332
|
+
if callable(prune):
|
|
333
|
+
try:
|
|
334
|
+
pruned, count = prune(copy.deepcopy(messages), protect_tail_count=getattr(compressor, "protect_last_n", 20), protect_tail_tokens=getattr(compressor, "tail_token_budget", None))
|
|
335
|
+
if count and isinstance(pruned, list) and len(pruned) == len(messages):
|
|
336
|
+
context = pruned
|
|
337
|
+
except Exception: # noqa: BLE001 - the hard cap below still bounds the context
|
|
338
|
+
log.debug("post-compression tool-result pruning failed", exc_info=True)
|
|
339
|
+
context = _cap_tool_results(context, compressor)
|
|
340
|
+
estimate = None
|
|
341
|
+
try:
|
|
342
|
+
from agent.model_metadata import estimate_request_tokens_rough
|
|
343
|
+
|
|
344
|
+
# The request carries the ephemeral prompt (surface, personality, delivery) after the system message.
|
|
345
|
+
prompts = [p for p in (system_message, getattr(agent, "ephemeral_system_prompt", None)) if isinstance(p, str) and p.strip()]
|
|
346
|
+
estimate = estimate_request_tokens_rough(context, system_prompt="\n\n".join(prompts), tools=getattr(agent, "tools", None) or None)
|
|
347
|
+
except Exception: # noqa: BLE001 - display-only figure
|
|
348
|
+
log.debug("post-compression context estimate failed", exc_info=True)
|
|
349
|
+
return context, estimate if isinstance(estimate, int) and estimate > 0 else None
|
|
350
|
+
|
|
351
|
+
|
|
234
352
|
def _clarify_timeout(params: dict) -> int:
|
|
235
353
|
"""Predecessor ``_clarify_timeout_seconds``: an explicit request value wins, else the Agent's resolver over config."""
|
|
236
354
|
explicit = params.get("clarify_timeout_seconds")
|
|
@@ -372,7 +490,7 @@ def _resolve_runtime(provider: str | None, model: str) -> dict:
|
|
|
372
490
|
def _turn_identity(session_id: str, workspace: str):
|
|
373
491
|
"""Bind this turn's identity to the calling context the way the predecessor's ``_set_turn_session_identity`` did:
|
|
374
492
|
the approval session key (so ``register_gateway_notify`` cards reach this turn and dangerous commands are gated
|
|
375
|
-
instead of auto-approved), the gateway session vars (platform ``webui``, chat/ui session ids), and the session cwd
|
|
493
|
+
instead of auto-approved), the gateway session vars (platform ``webui``, chat/ui session ids, profile), and the session cwd
|
|
376
494
|
(so terminals and AGENTS.md discovery run in the selected workspace, not the sidecar's launch directory). Every
|
|
377
495
|
binding is a context variable, so concurrent turns cannot overwrite each other. Missing Agent surfaces are logged,
|
|
378
496
|
not fatal."""
|
|
@@ -388,10 +506,14 @@ def _turn_identity(session_id: str, workspace: str):
|
|
|
388
506
|
log.debug("per-turn approval session-key bind failed", exc_info=True)
|
|
389
507
|
try:
|
|
390
508
|
from gateway import session_context as sc
|
|
509
|
+
from hermes_cli.profiles import get_active_profile_name
|
|
510
|
+
from hermes_constants import get_hermes_home, profile_name_for_home
|
|
391
511
|
|
|
512
|
+
# The persistent Docker sandbox is keyed by the profile name; unbound, every profile reuses "default".
|
|
513
|
+
profile = profile_name_for_home(get_hermes_home()) or get_active_profile_name()
|
|
392
514
|
pairs = [
|
|
393
515
|
(sc._SESSION_KEY, session_id), (sc._SESSION_UI_SESSION_ID, session_id), (sc._SESSION_PLATFORM, "webui"),
|
|
394
|
-
(sc._SESSION_CHAT_ID, session_id), (sc._SESSION_ID, session_id),
|
|
516
|
+
(sc._SESSION_CHAT_ID, session_id), (sc._SESSION_ID, session_id), (sc._SESSION_PROFILE, profile),
|
|
395
517
|
]
|
|
396
518
|
for var, value in pairs:
|
|
397
519
|
tok = var.set(value)
|
|
@@ -415,6 +537,87 @@ def _turn_identity(session_id: str, workspace: str):
|
|
|
415
537
|
log.debug("per-turn identity reset failed", exc_info=True)
|
|
416
538
|
|
|
417
539
|
|
|
540
|
+
#: Older configs named the CLI composite ``hermes``; the Agent registers it split in two (predecessor
|
|
541
|
+
#: ``_LEGACY_CLI_TOOLSET_ALIASES``).
|
|
542
|
+
_LEGACY_TOOLSET_ALIASES = {"hermes": ("hermes-cli", "hermes-api-server")}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _turn_toolsets(toolsets) -> list[str]:
|
|
546
|
+
"""The session's override as sent, else the profile's configured toolsets. ``None`` would hand the Agent every
|
|
547
|
+
registered toolset."""
|
|
548
|
+
return toolsets if isinstance(toolsets, list) else _profile_toolsets()
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _profile_toolsets() -> list[str]:
|
|
552
|
+
"""The profile's ``platform_toolsets.cli`` through the Agent's own resolver (default-off toolsets stay off), like
|
|
553
|
+
``hermes chat``. Runs under the call's ``scoped_home``."""
|
|
554
|
+
from hermes_cli.config import load_config
|
|
555
|
+
from hermes_cli.tools_config import _get_platform_tools
|
|
556
|
+
|
|
557
|
+
resolved = sorted(_get_platform_tools(load_config() or {}, "cli"))
|
|
558
|
+
return list(dict.fromkeys(name for raw in resolved for name in _LEGACY_TOOLSET_ALIASES.get(raw, (raw,))))
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _discover_mcp_tools(run: _Run) -> None:
|
|
562
|
+
"""Connect the profile's MCP servers before the turn's agent snapshots its tools, like the predecessor's turn start.
|
|
563
|
+
The Agent keys connections by profile scope and skips live ones, so later turns are cheap. Runs under the call's
|
|
564
|
+
``scoped_home``; a failure leaves the turn without MCP tools rather than failing it. A Stop does not wait for a slow
|
|
565
|
+
server: the turn moves on to its cancelled end while the connect finishes in the background."""
|
|
566
|
+
from .commands import retire_unscoped_launch_mcp_servers
|
|
567
|
+
|
|
568
|
+
done = threading.Event()
|
|
569
|
+
|
|
570
|
+
def discover() -> None:
|
|
571
|
+
try:
|
|
572
|
+
from tools.mcp_tool_discovery import discover_mcp_tools
|
|
573
|
+
|
|
574
|
+
retire_unscoped_launch_mcp_servers()
|
|
575
|
+
discover_mcp_tools()
|
|
576
|
+
except Exception: # noqa: BLE001
|
|
577
|
+
log.warning("MCP discovery failed", exc_info=True)
|
|
578
|
+
finally:
|
|
579
|
+
done.set()
|
|
580
|
+
|
|
581
|
+
# The copied context carries the turn's home override and secret scope into the worker.
|
|
582
|
+
threading.Thread(target=contextvars.copy_context().run, args=(discover,), daemon=True, name=f"chat-mcp-{run.stream_id[:8]}").start()
|
|
583
|
+
while not done.wait(0.25):
|
|
584
|
+
if run.cancel.is_set() or run.ctx.cancelled:
|
|
585
|
+
return
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _profile_fallback_chain() -> list[dict] | None:
|
|
589
|
+
"""The profile's fallback routes (``fallback_providers`` first, then legacy ``fallback_model``, deduplicated)
|
|
590
|
+
through the Agent's own resolver, like the CLI and gateway. Runs under the call's ``scoped_home``."""
|
|
591
|
+
from hermes_cli.config import load_config
|
|
592
|
+
from hermes_cli.fallback_config import get_fallback_chain
|
|
593
|
+
|
|
594
|
+
return get_fallback_chain(load_config() or {}) or None
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _main_model_request_overrides(model: str, provider) -> dict | None:
|
|
598
|
+
"""Predecessor ``_main_model_request_overrides``: the profile's ``model.service_tier`` and ``model.extra_body``
|
|
599
|
+
(written by ``/api/model/set``) apply only to turns on that configured main model. Runs under ``scoped_home``."""
|
|
600
|
+
from hermes_cli.config import load_config
|
|
601
|
+
|
|
602
|
+
model_cfg = (load_config() or {}).get("model")
|
|
603
|
+
if not isinstance(model_cfg, dict):
|
|
604
|
+
return None
|
|
605
|
+
main_model = str(model_cfg.get("default") or model_cfg.get("name") or "").strip()
|
|
606
|
+
main_provider = str(model_cfg.get("provider") or "").strip().lower()
|
|
607
|
+
if not main_model or main_model != model.strip():
|
|
608
|
+
return None
|
|
609
|
+
# An unset or ``auto`` main provider resolves at runtime, so the model id alone identifies the main model.
|
|
610
|
+
if main_provider not in ("", "auto") and main_provider != str(provider or "").strip().lower():
|
|
611
|
+
return None
|
|
612
|
+
overrides: dict = {}
|
|
613
|
+
if str(model_cfg.get("service_tier") or "").strip().lower() == "priority":
|
|
614
|
+
overrides["service_tier"] = "priority"
|
|
615
|
+
extra_body = model_cfg.get("extra_body")
|
|
616
|
+
if isinstance(extra_body, dict) and extra_body:
|
|
617
|
+
overrides["extra_body"] = extra_body
|
|
618
|
+
return overrides or None
|
|
619
|
+
|
|
620
|
+
|
|
418
621
|
def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, kwargs: dict) -> str:
|
|
419
622
|
"""Cache identity of an ``AIAgent``: everything its constructor bound from the resolved runtime, so a rotated key,
|
|
420
623
|
a different API mode, ACP command, or credential pool never reuses an agent built for the old bundle. The key
|
|
@@ -430,6 +633,9 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
|
|
|
430
633
|
"max_iterations": kwargs.get("max_iterations"), "max_tokens": kwargs.get("max_tokens"),
|
|
431
634
|
# Bound at construction too: a reasoning-effort change from the composer must build a fresh agent.
|
|
432
635
|
"reasoning_config": kwargs.get("reasoning_config"),
|
|
636
|
+
"request_overrides": kwargs.get("request_overrides"),
|
|
637
|
+
# Entries may carry their own ``api_key``.
|
|
638
|
+
"fallback_model": hashlib.sha256(json.dumps(kwargs.get("fallback_model"), sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16],
|
|
433
639
|
}
|
|
434
640
|
return json.dumps(bundle, sort_keys=True, default=str)
|
|
435
641
|
|
|
@@ -440,19 +646,164 @@ def _evict_idle_agents_locked() -> None:
|
|
|
440
646
|
return
|
|
441
647
|
with _RUNS_LOCK:
|
|
442
648
|
live = {sid for sid, sid_stream in _RUNS_BY_SESSION.items() if (r := _RUNS.get(sid_stream)) is not None and not r.finished.is_set()}
|
|
649
|
+
dropped = []
|
|
443
650
|
for sid in list(_AGENT_CACHE):
|
|
444
651
|
if len(_AGENT_CACHE) <= _AGENT_CACHE_MAX:
|
|
445
652
|
break
|
|
446
653
|
if sid not in live:
|
|
447
|
-
_AGENT_CACHE.pop(sid
|
|
654
|
+
dropped.append(_AGENT_CACHE.pop(sid))
|
|
655
|
+
_release_agents_locked(dropped)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _release_agent(agent, home) -> threading.Thread:
|
|
659
|
+
"""The gateway's soft eviction on a daemon thread under the owning profile: end the memory session with the
|
|
660
|
+
transcript (``on_session_end``, then provider shutdown), then release the LLM clients (the Codex app-server child,
|
|
661
|
+
httpx pools) while session tool state stays for a resumed session."""
|
|
662
|
+
|
|
663
|
+
def call(name: str, *args) -> bool:
|
|
664
|
+
method = getattr(agent, name, None)
|
|
665
|
+
if not callable(method):
|
|
666
|
+
return True
|
|
667
|
+
try:
|
|
668
|
+
method(*args)
|
|
669
|
+
except Exception: # noqa: BLE001 - one failed phase never skips the next
|
|
670
|
+
log.warning("evicted agent %s() failed", name, exc_info=True)
|
|
671
|
+
return False
|
|
672
|
+
return True
|
|
673
|
+
|
|
674
|
+
def release() -> None:
|
|
675
|
+
released = False
|
|
676
|
+
try:
|
|
677
|
+
with scoped_home(home):
|
|
678
|
+
messages = getattr(agent, "_session_messages", None)
|
|
679
|
+
flushed = call("shutdown_memory_provider", messages if isinstance(messages, list) else None)
|
|
680
|
+
released = True
|
|
681
|
+
thread.failed = not (call("release_clients") and flushed)
|
|
682
|
+
except Exception: # noqa: BLE001 - the profile scope could not be entered
|
|
683
|
+
log.warning("releasing an evicted agent under its profile failed", exc_info=True)
|
|
684
|
+
thread.failed = True
|
|
685
|
+
finally:
|
|
686
|
+
if not released:
|
|
687
|
+
# The memory flush needs the profile; the LLM clients and the Codex child close regardless.
|
|
688
|
+
call("release_clients")
|
|
689
|
+
_release_session_db(getattr(agent, "_talaria_session_db", None))
|
|
690
|
+
with _RELEASES_LOCK:
|
|
691
|
+
_RELEASES.pop(thread, None)
|
|
692
|
+
|
|
693
|
+
thread = threading.Thread(target=release, daemon=True, name="chat-agent-release")
|
|
694
|
+
thread.failed = False # type: ignore[attr-defined] - read by profile deletion
|
|
695
|
+
with _RELEASES_LOCK:
|
|
696
|
+
_RELEASES[thread] = home
|
|
697
|
+
thread.start()
|
|
698
|
+
return thread
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _release_agents_locked(entries) -> list[threading.Thread]:
|
|
702
|
+
"""Release cache entries that just left the cache (caller holds ``_AGENT_CACHE_LOCK``). An agent a turn still holds
|
|
703
|
+
keeps its memory provider and clients; that turn releases it when it ends."""
|
|
704
|
+
with _RUNS_LOCK:
|
|
705
|
+
held = [run.agent for run in _RUNS.values()] + _COMMITTING
|
|
706
|
+
return [_release_agent(agent, home) for agent, _signature, home in entries if not any(agent is other for other in held)]
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def _release_if_dropped_locked(agent, home) -> None:
|
|
710
|
+
"""A holder (turn or commit) is done with ``agent``: release it if it left the cache meanwhile and nothing else holds
|
|
711
|
+
it (caller holds ``_AGENT_CACHE_LOCK``)."""
|
|
712
|
+
if agent is not None and not any(entry[0] is agent for entry in _AGENT_CACHE.values()):
|
|
713
|
+
_release_agents_locked([(agent, None, home)])
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _join(threads, deadline: float) -> None:
|
|
717
|
+
for thread in threads:
|
|
718
|
+
thread.join(max(0.0, deadline - time.monotonic()))
|
|
448
719
|
|
|
449
720
|
|
|
450
721
|
def evict_all_agents() -> int:
|
|
451
722
|
"""Drop every cached agent (credentials or environment changed underneath them)."""
|
|
452
723
|
with _AGENT_CACHE_LOCK:
|
|
453
|
-
|
|
724
|
+
entries = list(_AGENT_CACHE.values())
|
|
454
725
|
_AGENT_CACHE.clear()
|
|
455
|
-
|
|
726
|
+
_release_agents_locked(entries)
|
|
727
|
+
return len(entries)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
|
|
731
|
+
"""Shutdown: release every cached agent, stop running turns (like a Stop) so each releases the agent it holds, and
|
|
732
|
+
wait, bounded, for those turns and every release still running."""
|
|
733
|
+
deadline = time.monotonic() + timeout
|
|
734
|
+
evict_all_agents()
|
|
735
|
+
with _RUNS_LOCK:
|
|
736
|
+
_DRAINING.set()
|
|
737
|
+
runs = list(_RUNS.values())
|
|
738
|
+
for run in runs:
|
|
739
|
+
run.cancel.set()
|
|
740
|
+
while time.monotonic() < deadline:
|
|
741
|
+
with _AGENT_CACHE_LOCK, _RUNS_LOCK:
|
|
742
|
+
if not _COMMITTING and not any(_RUNS.get(run.stream_id) is run for run in runs):
|
|
743
|
+
break
|
|
744
|
+
time.sleep(0.05)
|
|
745
|
+
# A turn may have cached its agent after the first eviction.
|
|
746
|
+
evict_all_agents()
|
|
747
|
+
with _RELEASES_LOCK:
|
|
748
|
+
threads = list(_RELEASES)
|
|
749
|
+
_join(threads, deadline)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def release_profile_agents(home) -> None:
|
|
753
|
+
"""Profile deletion: release the profile's cached agents and wait for every release running under its home. Fails
|
|
754
|
+
rather than let the home go while a memory flush still runs under it."""
|
|
755
|
+
target = os.path.realpath(home)
|
|
756
|
+
with _AGENT_CACHE_LOCK:
|
|
757
|
+
entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
|
|
758
|
+
_release_agents_locked(entries)
|
|
759
|
+
with _RELEASES_LOCK:
|
|
760
|
+
threads = [thread for thread, owner in _RELEASES.items() if os.path.realpath(owner) == target]
|
|
761
|
+
_join(threads, time.monotonic() + _PROFILE_RELEASE_TIMEOUT)
|
|
762
|
+
if any(thread.is_alive() for thread in threads):
|
|
763
|
+
raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
|
|
764
|
+
if any(thread.failed for thread in threads):
|
|
765
|
+
raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _acquire_session_db(home):
|
|
769
|
+
"""TAL-529: the profile's ``state.db`` from the Agent's shared-handle registry, one reference per agent (released with
|
|
770
|
+
it), so the turn's conversation reaches ``hermes sessions``, CLI resume, and ``session_search``. None when it cannot
|
|
771
|
+
open: the turn runs without it, like the predecessor's ``_build_session_db_for_stream``."""
|
|
772
|
+
try:
|
|
773
|
+
from hermes_state_registry import acquire
|
|
774
|
+
|
|
775
|
+
return acquire(Path(home) / "state.db")
|
|
776
|
+
except Exception: # noqa: BLE001
|
|
777
|
+
log.warning("state.db unavailable under %s; this agent's turns will not reach it", home, exc_info=True)
|
|
778
|
+
return None
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _release_session_db(db) -> None:
|
|
782
|
+
if db is None:
|
|
783
|
+
return
|
|
784
|
+
try:
|
|
785
|
+
from hermes_state_registry import release_or_close
|
|
786
|
+
|
|
787
|
+
release_or_close(db)
|
|
788
|
+
except Exception: # noqa: BLE001
|
|
789
|
+
log.warning("releasing an agent's state.db handle failed", exc_info=True)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _execution_session_id(db, session_id: str) -> str:
|
|
793
|
+
"""Predecessor ``_resolve_agent_execution_session_id``: a new agent for a session the Agent rotated by compression
|
|
794
|
+
(the old agent went with a sidecar restart or a runtime change) runs on the live tip, with the server's history,
|
|
795
|
+
rather than the closed Web id. The tip qualifies as in the Agent's own adoption: a different, still-open row. Any
|
|
796
|
+
doubt keeps the Web id."""
|
|
797
|
+
try:
|
|
798
|
+
tip = db.get_compression_tip(session_id)
|
|
799
|
+
if tip and tip != session_id:
|
|
800
|
+
row = db.get_session(tip)
|
|
801
|
+
if row and row.get("ended_at") is None:
|
|
802
|
+
log.info("agent for %s runs on its live compression tip %s", session_id, tip)
|
|
803
|
+
return str(tip)
|
|
804
|
+
except Exception: # noqa: BLE001
|
|
805
|
+
log.warning("compression tip lookup failed for %s", session_id, exc_info=True)
|
|
806
|
+
return session_id
|
|
456
807
|
|
|
457
808
|
|
|
458
809
|
def _agent_class():
|
|
@@ -463,6 +814,15 @@ def _agent_class():
|
|
|
463
814
|
return AIAgent
|
|
464
815
|
|
|
465
816
|
|
|
817
|
+
def _max_iterations_summary_request() -> str:
|
|
818
|
+
"""The user row the Agent appends to ask for a summary once its tool budget runs out; the server drops it."""
|
|
819
|
+
try:
|
|
820
|
+
from agent.context_compressor import MAX_ITERATIONS_SUMMARY_REQUEST
|
|
821
|
+
except Exception: # noqa: BLE001
|
|
822
|
+
return ""
|
|
823
|
+
return str(MAX_ITERATIONS_SUMMARY_REQUEST)
|
|
824
|
+
|
|
825
|
+
|
|
466
826
|
def _supported(cls, name: str) -> bool:
|
|
467
827
|
"""Whether ``cls.__init__`` accepts ``name`` (named, or through ``**kwargs``)."""
|
|
468
828
|
try:
|
|
@@ -472,6 +832,64 @@ def _supported(cls, name: str) -> bool:
|
|
|
472
832
|
return name in parameters or any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters.values())
|
|
473
833
|
|
|
474
834
|
|
|
835
|
+
def _approval_notifier(session_id: str, emit):
|
|
836
|
+
"""The Agent's gateway notify callback for one turn: emits each parked approval, then ``approval_resolved``
|
|
837
|
+
(id plus the Agent's reason) once its wait ends by any path, an ``approvals.timeout`` expiry included (TAL-514)."""
|
|
838
|
+
try:
|
|
839
|
+
from tools.approval import register_gateway_settle
|
|
840
|
+
except Exception: # noqa: BLE001
|
|
841
|
+
register_gateway_settle = None
|
|
842
|
+
|
|
843
|
+
def notify(data):
|
|
844
|
+
payload = dict(data or {})
|
|
845
|
+
payload.setdefault("session_id", session_id)
|
|
846
|
+
payload.setdefault("approval_id", str(payload.get("request_id") or uuid.uuid4().hex))
|
|
847
|
+
emit("approval", payload)
|
|
848
|
+
request_id = str(payload.get("request_id") or "")
|
|
849
|
+
if register_gateway_settle is None or not request_id:
|
|
850
|
+
return
|
|
851
|
+
approval_id = payload["approval_id"]
|
|
852
|
+
|
|
853
|
+
def settle(reason):
|
|
854
|
+
emit("approval_resolved", {"approval_id": approval_id, "session_id": session_id, "reason": str(reason or "")})
|
|
855
|
+
|
|
856
|
+
# False: the wait already ended between the emit and this registration.
|
|
857
|
+
if not register_gateway_settle(session_id, request_id, settle):
|
|
858
|
+
settle("resolved")
|
|
859
|
+
|
|
860
|
+
return notify
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
#: The Agent's compression-start lifecycle lines: "Compacting context" (and its heartbeat), pre-API, 413, and too-large
|
|
864
|
+
#: compression. Preflight and idle notices are left out: "Compacting context" follows them once compression proceeds.
|
|
865
|
+
_COMPRESSION_START_MARKERS = ("compacting context", "pre-api compression:", "context too large", "— compressing (", "- compressing (", "compression attempt")
|
|
866
|
+
_COMPRESSION_NOT_STARTED = ("skipping", "defer", "cooldown", "will not start")
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _is_compression_start(lower: str) -> bool:
|
|
870
|
+
"""Predecessor ``_is_agent_compression_start_status``: only real start notices, never skips or post-compress chatter."""
|
|
871
|
+
if any(marker in lower for marker in _COMPRESSION_NOT_STARTED):
|
|
872
|
+
return False
|
|
873
|
+
if "compressed" in lower and "compressing" not in lower and "compression attempt" not in lower:
|
|
874
|
+
return False
|
|
875
|
+
return any(marker in lower for marker in _COMPRESSION_START_MARKERS)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def _status_events(kind, text: str) -> list[str]:
|
|
879
|
+
"""Events for one Agent status line: the compressing card, a fallback warning, and the turn's terminal error."""
|
|
880
|
+
lower = text.lower()
|
|
881
|
+
events = ["terminal_error"] if "non-retryable error" in lower else []
|
|
882
|
+
if str(kind or "").strip().lower() == "lifecycle":
|
|
883
|
+
if _is_compression_start(lower):
|
|
884
|
+
return events + ["compressing"]
|
|
885
|
+
# The Agent's own compression retries ("Compressed N → M messages, retrying...") are not provider fallbacks.
|
|
886
|
+
if "compress" in lower or "context reduced to" in lower:
|
|
887
|
+
return events
|
|
888
|
+
if "fallback" in lower or "rate limit" in lower or ("retry" in lower and "non-retryable" not in lower):
|
|
889
|
+
events.append("warning")
|
|
890
|
+
return events
|
|
891
|
+
|
|
892
|
+
|
|
475
893
|
def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn, one function
|
|
476
894
|
session_id = str(params.get("session_id") or "").strip()
|
|
477
895
|
stream_id = str(params.get("stream_id") or "").strip()
|
|
@@ -487,10 +905,13 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
487
905
|
history = []
|
|
488
906
|
if not isinstance(history, list):
|
|
489
907
|
raise InvalidParams("conversation_history must be a list")
|
|
490
|
-
toolsets = params.get("enabled_toolsets")
|
|
908
|
+
toolsets = _turn_toolsets(params.get("enabled_toolsets"))
|
|
491
909
|
system_message = params.get("system_message")
|
|
910
|
+
home = profile_home_param(params)
|
|
492
911
|
run = _Run(stream_id, session_id, ctx)
|
|
493
912
|
with _RUNS_LOCK:
|
|
913
|
+
if _DRAINING.is_set():
|
|
914
|
+
raise RpcError("The sidecar is shutting down.", condition="sidecar_unavailable")
|
|
494
915
|
prior = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
|
|
495
916
|
# A previous turn for this session that never returned (a tool ignoring its interrupt) still owns the cached
|
|
496
917
|
# agent; this turn must not share, rebind, or un-interrupt that live instance.
|
|
@@ -499,17 +920,24 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
499
920
|
_RUNS_BY_SESSION[session_id] = stream_id
|
|
500
921
|
raw_emit = ctx.emit
|
|
501
922
|
steer_state = {"last": None}
|
|
923
|
+
usage_state = {"last": None}
|
|
502
924
|
|
|
503
925
|
def emit(event, data=None):
|
|
504
926
|
"""Predecessor ``_webui_steer_events_before``: before content frames, report the Agent's pending steer text
|
|
505
|
-
whenever it changed so the server can mark consumed steers live rather than only after ``done``.
|
|
927
|
+
whenever it changed so the server can mark consumed steers live rather than only after ``done``. TAL-397: the
|
|
928
|
+
Agent's session counters ride the same boundary, so the server meters cost and tokens while the turn runs."""
|
|
506
929
|
if event in ("token", "reasoning", "interim_assistant", "tool", "tool_complete") and run.agent is not None:
|
|
507
930
|
pending = _agent_pending_steer_text(run.agent)
|
|
508
931
|
if pending != steer_state["last"]:
|
|
509
932
|
steer_state["last"] = pending
|
|
510
933
|
raw_emit("steer_pending", {"text": pending})
|
|
934
|
+
usage = _usage(run.agent)
|
|
935
|
+
if usage != usage_state["last"]:
|
|
936
|
+
usage_state["last"] = usage
|
|
937
|
+
raw_emit("usage", usage)
|
|
511
938
|
raw_emit(event, data)
|
|
512
939
|
try:
|
|
940
|
+
_discover_mcp_tools(run)
|
|
513
941
|
runtime = _resolve_runtime(provider, model)
|
|
514
942
|
resolved_model = model or str(runtime.get("model") or "")
|
|
515
943
|
resolved_provider = provider or runtime.get("provider")
|
|
@@ -560,6 +988,13 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
560
988
|
call["snippet"] = snippet
|
|
561
989
|
break
|
|
562
990
|
payload = {"event_type": "tool.completed", "name": name, "preview": snippet, "args": _args_snapshot(args), "tid": tid, "raw_result": _raw_result(function_result)}
|
|
991
|
+
# TAL-397: ``raw_result`` caps nested values, so the todo tool's list travels whole for the server's ``todo_state``.
|
|
992
|
+
if name == "todo" and function_result is not None:
|
|
993
|
+
payload["todo_result"] = function_result if isinstance(function_result, str) else json.dumps(function_result, default=str)
|
|
994
|
+
# TAL-448: likewise a result's unified ``diff``, so the server's ``edit_diff`` counts cover the whole change.
|
|
995
|
+
diff = _result_diff(function_result)
|
|
996
|
+
if diff is not None:
|
|
997
|
+
payload["result_diff"] = diff
|
|
563
998
|
cost = _delegation_cost_usd(name, function_result)
|
|
564
999
|
if cost is not None:
|
|
565
1000
|
payload["cost_usd"] = cost
|
|
@@ -578,15 +1013,13 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
578
1013
|
|
|
579
1014
|
def on_status(kind, message):
|
|
580
1015
|
text = str(message or "").strip()
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
elif "non-retryable error" in lower:
|
|
589
|
-
emit("status", {"kind": "terminal_error", "message": text})
|
|
1016
|
+
for event in _status_events(kind, text):
|
|
1017
|
+
if event == "compressing":
|
|
1018
|
+
emit("compressing", {"session_id": session_id, "message": "Compressing context"})
|
|
1019
|
+
elif event == "warning":
|
|
1020
|
+
emit("warning", {"type": "fallback", "message": text})
|
|
1021
|
+
else:
|
|
1022
|
+
emit("status", {"kind": "terminal_error", "message": text})
|
|
590
1023
|
|
|
591
1024
|
def clarify_callback(question, choices, multi_select=False, questions=None):
|
|
592
1025
|
choices_list = [str(c) for c in (choices or [])]
|
|
@@ -626,15 +1059,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
626
1059
|
from tools.approval import register_gateway_notify, unregister_gateway_notify
|
|
627
1060
|
except Exception: # noqa: BLE001
|
|
628
1061
|
register_gateway_notify = unregister_gateway_notify = None
|
|
629
|
-
if register_gateway_notify is not None
|
|
630
|
-
def _approval_cb(data):
|
|
631
|
-
payload = dict(data or {})
|
|
632
|
-
payload.setdefault("session_id", session_id)
|
|
633
|
-
payload.setdefault("approval_id", str(payload.get("request_id") or uuid.uuid4().hex))
|
|
634
|
-
emit("approval", payload)
|
|
635
|
-
approval_cb = _approval_cb
|
|
636
|
-
else:
|
|
637
|
-
approval_cb = None
|
|
1062
|
+
approval_cb = _approval_notifier(session_id, emit) if register_gateway_notify is not None else None
|
|
638
1063
|
|
|
639
1064
|
kwargs: dict = dict(
|
|
640
1065
|
model=resolved_model,
|
|
@@ -643,7 +1068,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
643
1068
|
api_key=runtime.get("api_key"),
|
|
644
1069
|
platform="webui",
|
|
645
1070
|
quiet_mode=True,
|
|
646
|
-
enabled_toolsets=toolsets
|
|
1071
|
+
enabled_toolsets=toolsets,
|
|
647
1072
|
session_id=session_id,
|
|
648
1073
|
stream_delta_callback=on_token,
|
|
649
1074
|
reasoning_callback=on_reasoning,
|
|
@@ -671,23 +1096,48 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
671
1096
|
reasoning_config = params.get("reasoning_config")
|
|
672
1097
|
if isinstance(reasoning_config, dict) and _supported(AIAgent, "reasoning_config"):
|
|
673
1098
|
kwargs["reasoning_config"] = reasoning_config
|
|
1099
|
+
fallback_chain = _profile_fallback_chain()
|
|
1100
|
+
if fallback_chain and _supported(AIAgent, "fallback_model"):
|
|
1101
|
+
kwargs["fallback_model"] = fallback_chain
|
|
1102
|
+
request_overrides = _main_model_request_overrides(resolved_model, resolved_provider)
|
|
1103
|
+
if request_overrides and _supported(AIAgent, "request_overrides"):
|
|
1104
|
+
kwargs["request_overrides"] = request_overrides
|
|
674
1105
|
signature = _agent_signature(resolved_model, resolved_provider, runtime, toolsets, str(params.get("profile_home")), kwargs)
|
|
675
1106
|
agent = None
|
|
1107
|
+
|
|
1108
|
+
def bind(bound) -> None:
|
|
1109
|
+
"""Claim the agent for this run under ``_AGENT_CACHE_LOCK``, so an eviction never releases it mid-turn."""
|
|
1110
|
+
run.prior_messages = getattr(bound, "_session_messages", None)
|
|
1111
|
+
run.agent = bound
|
|
1112
|
+
|
|
676
1113
|
if not session_busy:
|
|
677
1114
|
with _AGENT_CACHE_LOCK:
|
|
678
1115
|
cached = _AGENT_CACHE.get(session_id)
|
|
679
1116
|
if cached and cached[1] == signature:
|
|
680
1117
|
agent = cached[0]
|
|
681
1118
|
_AGENT_CACHE.move_to_end(session_id)
|
|
1119
|
+
bind(agent)
|
|
682
1120
|
if agent is not None:
|
|
683
1121
|
for name in ("stream_delta_callback", "reasoning_callback", "tool_progress_callback", "clarify_callback", "interim_assistant_callback", "tool_start_callback", "tool_complete_callback", "status_callback"):
|
|
684
1122
|
if name in kwargs and hasattr(agent, name):
|
|
685
1123
|
setattr(agent, name, kwargs[name])
|
|
686
1124
|
else:
|
|
687
|
-
|
|
1125
|
+
# A ``/btw`` side question's throwaway session stays out of state.db.
|
|
1126
|
+
session_db = _acquire_session_db(home) if not params.get("ephemeral") and _supported(AIAgent, "session_db") else None
|
|
1127
|
+
if session_db is not None:
|
|
1128
|
+
kwargs["session_db"] = session_db
|
|
1129
|
+
kwargs["session_id"] = _execution_session_id(session_db, session_id)
|
|
1130
|
+
try:
|
|
1131
|
+
agent = AIAgent(**kwargs)
|
|
1132
|
+
except BaseException:
|
|
1133
|
+
_release_session_db(session_db)
|
|
1134
|
+
raise
|
|
1135
|
+
agent._talaria_session_db = session_db # released with the agent (``_release_agent``)
|
|
688
1136
|
with _AGENT_CACHE_LOCK:
|
|
689
|
-
|
|
690
|
-
_AGENT_CACHE.
|
|
1137
|
+
bind(agent)
|
|
1138
|
+
replaced = _AGENT_CACHE.pop(session_id, None)
|
|
1139
|
+
_AGENT_CACHE[session_id] = (agent, signature, home)
|
|
1140
|
+
_release_agents_locked([replaced] if replaced else [])
|
|
691
1141
|
_evict_idle_agents_locked()
|
|
692
1142
|
# Predecessor ``agent.ephemeral_system_prompt``: personality, surface context, progress guidance, and delivery
|
|
693
1143
|
# hints travel as runtime instructions that are never persisted to history.
|
|
@@ -701,8 +1151,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
701
1151
|
with _APPROVAL_CB_LOCK:
|
|
702
1152
|
register_gateway_notify(session_id, approval_cb)
|
|
703
1153
|
_APPROVAL_CB_OWNER[session_id] = stream_id
|
|
704
|
-
|
|
705
|
-
run.agent = agent
|
|
1154
|
+
usage_state["last"] = _usage(agent)
|
|
706
1155
|
compressions_before = int(getattr(getattr(agent, "context_compressor", None), "compression_count", 0) or 0)
|
|
707
1156
|
|
|
708
1157
|
# A cached agent may still carry the interrupt a previous cancel left behind; the Agent keeps a pending
|
|
@@ -742,8 +1191,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
742
1191
|
run_params = set()
|
|
743
1192
|
if system_message and ("system_message" in run_params or not run_params):
|
|
744
1193
|
run_kwargs["system_message"] = system_message
|
|
745
|
-
|
|
746
|
-
|
|
1194
|
+
# The server's clean user text (no workspace prefix) is what state.db stores; the Agent keeps the sent bytes beside it.
|
|
1195
|
+
persist = params.get("persist_user_message")
|
|
1196
|
+
if not (isinstance(persist, str) and persist.strip()):
|
|
1197
|
+
persist = user_message if isinstance(user_message, str) else None
|
|
1198
|
+
if "persist_user_message" in run_params and persist is not None:
|
|
1199
|
+
run_kwargs["persist_user_message"] = persist
|
|
747
1200
|
error: str | None = None
|
|
748
1201
|
result: dict = {}
|
|
749
1202
|
try:
|
|
@@ -772,30 +1225,48 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
772
1225
|
error = str(last_error)
|
|
773
1226
|
if not error and isinstance(result.get("error"), str) and result.get("error"):
|
|
774
1227
|
error = str(result["error"])
|
|
775
|
-
|
|
1228
|
+
# The Agent ends a failed turn with ``failed``/``partial`` (never ``status``), often after text streamed and with
|
|
1229
|
+
# the turn's messages; a failed turn is an error whatever it carries.
|
|
1230
|
+
failed = bool(result.get("failed"))
|
|
1231
|
+
partial = bool(result.get("partial"))
|
|
1232
|
+
if not error and failed and result.get("final_response"):
|
|
1233
|
+
error = str(result["final_response"])
|
|
1234
|
+
status = "cancelled" if cancelled else ("error" if failed or (error and not result.get("messages")) else "completed")
|
|
776
1235
|
pending_steer = str(result.get("pending_steer") or "") if isinstance(result, dict) else ""
|
|
1236
|
+
messages = [m for m in (result.get("messages") or []) if isinstance(m, dict)]
|
|
1237
|
+
compressed = compressions_after > compressions_before
|
|
1238
|
+
context_messages, context_estimate = _post_compression_context(agent, messages, system_message) if compressed and messages and status == "completed" else (None, None)
|
|
777
1239
|
return {
|
|
778
1240
|
"status": status,
|
|
779
|
-
"messages":
|
|
1241
|
+
"messages": messages,
|
|
1242
|
+
"context_messages": context_messages,
|
|
1243
|
+
"post_compression_context_tokens_estimate": context_estimate,
|
|
780
1244
|
"final_response": str(result.get("final_response") or ""),
|
|
781
1245
|
"error": error,
|
|
782
|
-
"
|
|
783
|
-
"
|
|
1246
|
+
"failed": failed,
|
|
1247
|
+
"partial": partial,
|
|
1248
|
+
"compression_exhausted": bool(result.get("compression_exhausted")),
|
|
1249
|
+
# The pinned Agent marks an exhausted tool budget only in ``turn_exit_reason`` (``max_iterations_reached(n/n)``).
|
|
1250
|
+
"tool_limit_reached": bool(result.get("tool_limit_reached") or result.get("max_iterations_reached") or str(result.get("turn_exit_reason") or "").startswith("max_iterations_reached")),
|
|
1251
|
+
"max_iterations_summary_request": _max_iterations_summary_request(),
|
|
784
1252
|
"usage": _usage(agent),
|
|
785
1253
|
"context": _context_length(agent),
|
|
786
1254
|
"model": str(getattr(agent, "model", None) or resolved_model),
|
|
787
1255
|
"provider": str(resolved_provider or ""),
|
|
788
|
-
"compressed":
|
|
1256
|
+
"compressed": compressed,
|
|
789
1257
|
"agent_session_id": str(getattr(agent, "session_id", None) or session_id),
|
|
790
1258
|
"token_sent": token_sent[0],
|
|
791
1259
|
"pending_steer": pending_steer,
|
|
792
1260
|
"live_tool_calls": live_tool_calls,
|
|
793
1261
|
}
|
|
794
1262
|
finally:
|
|
795
|
-
with
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
_RUNS_BY_SESSION.
|
|
1263
|
+
with _AGENT_CACHE_LOCK:
|
|
1264
|
+
with _RUNS_LOCK:
|
|
1265
|
+
_RUNS.pop(stream_id, None)
|
|
1266
|
+
if _RUNS_BY_SESSION.get(session_id) == stream_id:
|
|
1267
|
+
_RUNS_BY_SESSION.pop(session_id, None)
|
|
1268
|
+
# An agent evicted or replaced while this turn held it is released now that the turn is over.
|
|
1269
|
+
_release_if_dropped_locked(run.agent, home)
|
|
799
1270
|
|
|
800
1271
|
|
|
801
1272
|
def _checkpoint_required() -> bool:
|
|
@@ -861,7 +1332,7 @@ def compress(ctx: CallContext, params: dict) -> dict:
|
|
|
861
1332
|
model = str(params.get("model") or "").strip()
|
|
862
1333
|
provider = str(params.get("model_provider") or "").strip() or None
|
|
863
1334
|
focus_topic = str(params.get("focus_topic") or "").strip()[:500] or None
|
|
864
|
-
toolsets = params.get("enabled_toolsets")
|
|
1335
|
+
toolsets = _turn_toolsets(params.get("enabled_toolsets"))
|
|
865
1336
|
runtime = _resolve_runtime(provider, model)
|
|
866
1337
|
if not runtime.get("api_key"):
|
|
867
1338
|
raise RpcError("No provider configured -- cannot compress.", condition="credential_missing")
|
|
@@ -875,7 +1346,7 @@ def compress(ctx: CallContext, params: dict) -> dict:
|
|
|
875
1346
|
api_key=runtime.get("api_key"),
|
|
876
1347
|
platform="webui",
|
|
877
1348
|
quiet_mode=True,
|
|
878
|
-
enabled_toolsets=toolsets
|
|
1349
|
+
enabled_toolsets=toolsets,
|
|
879
1350
|
session_id=session_id,
|
|
880
1351
|
)
|
|
881
1352
|
for name, value in (
|
|
@@ -1017,33 +1488,50 @@ def register(registry) -> None:
|
|
|
1017
1488
|
|
|
1018
1489
|
@registry.method("chat.evict_agent", requires_agent=False)
|
|
1019
1490
|
def evict_(ctx: CallContext, params: dict) -> dict:
|
|
1491
|
+
"""Drop a session's cached agent. Only delete/clear (``clear_session``) end its approval grants, parked
|
|
1492
|
+
approvals and kernels; a model switch, truncate or compress leaves a running turn's agent in place."""
|
|
1020
1493
|
session_id = str(params.get("session_id") or "").strip()
|
|
1494
|
+
end_session = params.get("clear_session") is True
|
|
1021
1495
|
with _AGENT_CACHE_LOCK:
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1496
|
+
with _RUNS_LOCK:
|
|
1497
|
+
run = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
|
|
1498
|
+
if not end_session and run is not None and not run.finished.is_set():
|
|
1499
|
+
return {"evicted": False}
|
|
1500
|
+
entry = _AGENT_CACHE.pop(session_id, None)
|
|
1501
|
+
_release_agents_locked([entry] if entry else [])
|
|
1502
|
+
if end_session:
|
|
1503
|
+
try:
|
|
1504
|
+
from tools.approval import clear_session
|
|
1505
|
+
clear_session(session_id)
|
|
1506
|
+
except Exception: # noqa: BLE001
|
|
1507
|
+
pass
|
|
1508
|
+
return {"evicted": entry is not None}
|
|
1029
1509
|
|
|
1030
1510
|
@registry.method("chat.commit_memory", requires_agent=False)
|
|
1031
1511
|
def commit_memory_(ctx: CallContext, params: dict) -> dict:
|
|
1032
|
-
"""Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left.
|
|
1512
|
+
"""Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left.
|
|
1513
|
+
Providers get the session's transcript and run under its profile's ``scoped_home``."""
|
|
1033
1514
|
session_id = str(params.get("session_id") or "").strip()
|
|
1515
|
+
home = profile_home_param(params)
|
|
1034
1516
|
with _AGENT_CACHE_LOCK:
|
|
1035
1517
|
cached = _AGENT_CACHE.get(session_id)
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1518
|
+
with _RUNS_LOCK:
|
|
1519
|
+
busy = _DRAINING.is_set() or (_RUNS_BY_SESSION.get(session_id) or "") in _RUNS
|
|
1520
|
+
agent = cached[0] if cached else None
|
|
1521
|
+
commit = getattr(agent, "commit_memory_session", None) if agent is not None else None
|
|
1522
|
+
if commit is None or busy:
|
|
1523
|
+
return {"committed": False}
|
|
1524
|
+
_COMMITTING.append(agent)
|
|
1042
1525
|
try:
|
|
1043
|
-
|
|
1526
|
+
with scoped_home(home):
|
|
1527
|
+
commit(list(getattr(agent, "_session_messages", None) or []))
|
|
1044
1528
|
except Exception as exc: # noqa: BLE001
|
|
1045
1529
|
log.warning("commit_memory_session() failed for session %s: %s", session_id, exc)
|
|
1046
1530
|
return {"committed": False}
|
|
1531
|
+
finally:
|
|
1532
|
+
with _AGENT_CACHE_LOCK:
|
|
1533
|
+
_COMMITTING.remove(agent)
|
|
1534
|
+
_release_if_dropped_locked(agent, cached[2])
|
|
1047
1535
|
return {"committed": True}
|
|
1048
1536
|
|
|
1049
1537
|
@registry.method("approval.respond", requires_agent=True)
|
|
@@ -1101,7 +1589,9 @@ def register(registry) -> None:
|
|
|
1101
1589
|
except Exception as exc: # noqa: BLE001
|
|
1102
1590
|
raise RpcError(f"approval module unavailable: {exc}", condition="agent_unavailable") from exc
|
|
1103
1591
|
(enable_session_yolo if enabled else disable_session_yolo)(session_id)
|
|
1104
|
-
released
|
|
1592
|
+
# Parked approvals are released `once`: a `session` or `always` grant would keep their patterns approved after
|
|
1593
|
+
# YOLO turns off, whatever choice the card that enabled it carried.
|
|
1594
|
+
released = resolve_gateway_approval(session_id, "once", resolve_all=True) if enabled else 0
|
|
1105
1595
|
return {"yolo_enabled": bool(is_session_yolo_enabled(session_id)), "released": int(released or 0)}
|
|
1106
1596
|
|
|
1107
1597
|
@registry.method("clarify.respond", requires_agent=False)
|