@maudecode/talaria-web 1.19.0 → 1.20.0
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 +8 -8
- package/dist/api/automation-raw.js +61 -6
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +8 -6
- package/dist/api/automation-router.js +7 -1
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +258 -33
- package/dist/api/chat-router.js +60 -25
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +4 -0
- package/dist/api/git-router.js +1 -1
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.js +13 -1
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +2 -2
- package/dist/api/session-visibility.js +2 -2
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +787 -107
- package/dist/api/sessions-router.js +16 -4
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +34 -13
- package/dist/api/settings-router.js +54 -17
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +41 -19
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +314 -32
- package/dist/api/tools-router.js +135 -38
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +15 -11
- package/dist/app.js.map +1 -1
- package/dist/auth/crypto.d.ts +3 -0
- package/dist/auth/crypto.js +18 -0
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/gate.js +5 -1
- package/dist/auth/gate.js.map +1 -1
- package/dist/cli/ctl.js +6 -1
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/main.js +23 -18
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/supervise.d.ts +22 -0
- package/dist/cli/supervise.js +80 -2
- package/dist/cli/supervise.js.map +1 -1
- package/dist/config/agent-config.d.ts +3 -0
- package/dist/config/agent-config.js +36 -9
- package/dist/config/agent-config.js.map +1 -1
- package/dist/fs/atomic.d.ts +3 -0
- package/dist/fs/atomic.js +19 -5
- package/dist/fs/atomic.js.map +1 -1
- package/dist/fs/stat.d.ts +6 -0
- package/dist/fs/stat.js +18 -2
- package/dist/fs/stat.js.map +1 -1
- package/dist/http/context.d.ts +14 -0
- package/dist/http/context.js +48 -5
- package/dist/http/context.js.map +1 -1
- package/dist/profiles/profiles.d.ts +2 -0
- package/dist/profiles/profiles.js +1 -0
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +11 -6
- package/dist/providers/catalog.js +76 -24
- package/dist/providers/catalog.js.map +1 -1
- package/dist/redact.js +38 -14
- package/dist/redact.js.map +1 -1
- package/dist/release.js +5 -3
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +85 -21
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +24 -21
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +2 -1
- package/dist/sessions/anchor.js +5 -1
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +23 -3
- package/dist/sessions/background-tasks.js +74 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +9 -2
- package/dist/sessions/cli-sessions.js +38 -57
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +8 -0
- package/dist/sessions/completions.js +55 -25
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/export.d.ts +2 -0
- package/dist/sessions/export.js +6 -2
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +44 -4
- package/dist/sessions/gateway-sessions.js +626 -90
- package/dist/sessions/gateway-sessions.js.map +1 -1
- package/dist/sessions/gateway-watcher.d.ts +10 -11
- package/dist/sessions/gateway-watcher.js +25 -21
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/list.d.ts +9 -2
- package/dist/sessions/list.js +38 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +11 -5
- package/dist/sessions/merge.js +59 -17
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +7 -4
- package/dist/sessions/prefix-index.js +21 -10
- package/dist/sessions/prefix-index.js.map +1 -1
- package/dist/sessions/recovery.d.ts +1 -1
- package/dist/sessions/recovery.js +4 -3
- package/dist/sessions/recovery.js.map +1 -1
- package/dist/sessions/service.d.ts +107 -37
- package/dist/sessions/service.js +490 -156
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +2 -0
- package/dist/sessions/session.js +4 -0
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/state-db-worker.js +22 -5
- package/dist/sessions/state-db-worker.js.map +1 -1
- package/dist/sessions/state-db.d.ts +106 -15
- package/dist/sessions/state-db.js +651 -236
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/state-saved.d.ts +1 -1
- package/dist/sessions/state-saved.js +35 -11
- package/dist/sessions/state-saved.js.map +1 -1
- package/dist/sessions/storage/backend.d.ts +10 -1
- package/dist/sessions/storage/backend.js.map +1 -1
- package/dist/sessions/storage/conformance.js +1 -1
- package/dist/sessions/storage/conformance.js.map +1 -1
- package/dist/sessions/storage/json-documents.d.ts +2 -0
- package/dist/sessions/storage/json-documents.js +70 -26
- package/dist/sessions/storage/json-documents.js.map +1 -1
- package/dist/sessions/storage/json-files.d.ts +13 -2
- package/dist/sessions/storage/json-files.js +126 -6
- package/dist/sessions/storage/json-files.js.map +1 -1
- package/dist/sessions/storage/json-journal.d.ts +6 -7
- package/dist/sessions/storage/json-journal.js +143 -73
- package/dist/sessions/storage/json-journal.js.map +1 -1
- package/dist/sessions/storage/json.d.ts +15 -1
- package/dist/sessions/storage/json.js +125 -33
- package/dist/sessions/storage/json.js.map +1 -1
- package/dist/sessions/store.d.ts +5 -1
- package/dist/sessions/store.js +67 -27
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +8 -7
- package/dist/sessions/streams.js +49 -14
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +3 -1
- package/dist/sessions/tool-display.js +6 -0
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.js +6 -2
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +23 -5
- package/dist/sessions/turn.js +168 -78
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sidecar/client.d.ts +24 -1
- package/dist/sidecar/client.js +118 -38
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/fake.d.ts +6 -2
- package/dist/sidecar/fake.js +15 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.d.ts +9 -4
- package/dist/spa.js +36 -12
- package/dist/spa.js.map +1 -1
- package/dist/startup-timing.d.ts +66 -0
- package/dist/startup-timing.js +129 -0
- package/dist/startup-timing.js.map +1 -0
- package/dist/test/fs-handles.d.ts +11 -0
- package/dist/test/fs-handles.js +31 -0
- package/dist/test/fs-handles.js.map +1 -0
- package/dist/tools/connectors.d.ts +1 -1
- package/dist/tools/connectors.js +2 -2
- package/dist/tools/connectors.js.map +1 -1
- package/dist/tools/crons.d.ts +8 -4
- package/dist/tools/crons.js +25 -18
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +29 -0
- package/dist/tools/dashboard-plugins.js +132 -0
- package/dist/tools/dashboard-plugins.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +25 -5
- package/dist/tools/gateway-restart.js +103 -23
- package/dist/tools/gateway-restart.js.map +1 -1
- package/dist/tools/health.d.ts +13 -1
- package/dist/tools/health.js +80 -22
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/insights.d.ts +1 -1
- package/dist/tools/insights.js +2 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/mcp-health.js +74 -24
- package/dist/tools/mcp-health.js.map +1 -1
- package/dist/tools/mcp.js +19 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/plugin-oauth.d.ts +14 -0
- package/dist/tools/plugin-oauth.js +53 -0
- package/dist/tools/plugin-oauth.js.map +1 -0
- package/dist/tools/prune-advice.d.ts +50 -0
- package/dist/tools/prune-advice.js +114 -0
- package/dist/tools/prune-advice.js.map +1 -0
- package/dist/tools/skills.d.ts +5 -2
- package/dist/tools/skills.js +9 -6
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +73 -7
- package/dist/tools/update-notifications.js +277 -43
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +8 -1
- package/dist/tools/updates.js +51 -17
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/media.js +69 -20
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.js +4 -14
- package/dist/workspace/preview.js.map +1 -1
- package/dist/workspace/rollback.d.ts +6 -2
- package/dist/workspace/rollback.js +17 -7
- package/dist/workspace/rollback.js.map +1 -1
- package/package.json +3 -3
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/agent_support.json +17 -14
- package/sidecar/scripts/suite_runner.py +183 -27
- package/sidecar/scripts/test.sh +5 -7
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +10 -3
- package/sidecar/talaria_sidecar/agent_mutation.py +159 -23
- package/sidecar/talaria_sidecar/agent_services.py +74 -11
- package/sidecar/talaria_sidecar/context_detail.py +59 -0
- package/sidecar/talaria_sidecar/home.py +97 -20
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +80 -18
- package/sidecar/talaria_sidecar/methods/chat.py +571 -106
- package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
- package/sidecar/talaria_sidecar/methods/commands.py +277 -16
- package/sidecar/talaria_sidecar/methods/config.py +73 -3
- package/sidecar/talaria_sidecar/methods/connectors.py +45 -13
- package/sidecar/talaria_sidecar/methods/cron.py +8 -2
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +24 -4
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +211 -27
- package/sidecar/talaria_sidecar/methods/kanban.py +2 -3
- package/sidecar/talaria_sidecar/methods/oauth.py +139 -11
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
- package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
- package/sidecar/talaria_sidecar/methods/process.py +70 -6
- package/sidecar/talaria_sidecar/methods/profiles.py +4 -3
- package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
- package/sidecar/talaria_sidecar/methods/skills.py +22 -7
- package/sidecar/talaria_sidecar/methods/state_db.py +128 -42
- package/sidecar/talaria_sidecar/methods/toolsets.py +4 -2
- package/sidecar/talaria_sidecar/methods/worktree.py +121 -2
- package/sidecar/talaria_sidecar/rpc.py +25 -3
- package/sidecar/talaria_sidecar/runtime.py +42 -3
- package/sidecar/talaria_sidecar/speed.py +13 -1
- package/sidecar/talaria_sidecar/startup_timing.py +119 -0
- package/static/dist/FILES.txt +241 -213
- package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
- package/static/dist/assets/AppShell-3isgvoro.js +8 -0
- package/static/dist/assets/Button-f4n6dapf.js +1 -0
- package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
- package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
- package/static/dist/assets/Dialog-hta5utr3.js +1 -0
- package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
- package/static/dist/assets/Field-b6ez48oz.js +1 -0
- package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
- package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
- package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
- package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
- package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
- package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
- package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
- package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
- package/static/dist/assets/Select-kr07928w.js +1 -0
- package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
- package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
- package/static/dist/assets/States-icab19yg.js +1 -0
- package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
- package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
- package/static/dist/assets/_app-ga1moszl.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
- package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
- package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
- package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
- package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
- package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
- package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
- package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
- package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
- package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
- package/static/dist/assets/_app.skills-n016rihd.js +1 -0
- package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
- package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
- package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
- package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
- package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
- package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
- package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
- package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
- package/static/dist/assets/archive-il7c6053.js +1 -0
- package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
- package/static/dist/assets/back-gu3nu279.js +1 -0
- package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-joedghi3.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
- package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-hyrjrohe.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
- package/static/dist/assets/cancel-bsnfo36u.js +1 -0
- package/static/dist/assets/channel-dsbtk01y.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-koouqbut.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
- package/static/dist/assets/{chunk-75Z2AOVW-bl4bhdnt.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
- package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
- package/static/dist/assets/{chunk-F27PBJKO-d8xxi0zi.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
- package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
- package/static/dist/assets/{chunk-GMAD6QVW-jro0hl73.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
- package/static/dist/assets/{chunk-GVQU2GXP-djn6gbyu.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-vw4p5p7o.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
- package/static/dist/assets/{chunk-L3NEJ4N5-n1hj77q8.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
- package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
- package/static/dist/assets/{chunk-P2QGCYS3-onf1hz6c.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-m8sqn7cs.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ek4smc8x.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-g9gfvefo.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
- package/static/dist/assets/{chunk-SVP7TREG-f9nfv2cu.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
- package/static/dist/assets/{chunk-TICWLB2K-bnk3m1mq.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
- package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
- package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
- package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
- package/static/dist/assets/copied-bz3gkazh.js +1 -0
- package/static/dist/assets/copy-jo006coq.js +1 -0
- package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
- package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
- package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
- package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
- package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
- package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
- package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
- package/static/dist/assets/dagre-o5jmjz46.js +1 -0
- package/static/dist/assets/{diagram-S7CK7UJ4-m6aeoe7c.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
- package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
- package/static/dist/assets/{diagram-VSXAHHWV-hbawi7hn.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
- package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
- package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
- package/static/dist/assets/dist-crc46di9.js +7 -0
- package/static/dist/assets/dist-etpoyscq.js +44 -0
- package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
- package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
- package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
- package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
- package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
- package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
- package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ma6jxj5i.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
- package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-hzltvsod.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
- package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
- package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
- package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
- package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
- package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
- package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
- package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
- package/static/dist/assets/host-fr6lni1p.js +1 -0
- package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
- package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
- package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
- package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
- package/static/dist/assets/index-73649w7o.js +10 -0
- package/static/dist/assets/index-lhf1fsdf.css +2 -0
- package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
- package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-e7bnwq7h.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
- package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
- package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
- package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
- package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-etd1bi6p.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
- package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
- package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
- package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
- package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
- package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-emb1g9dz.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
- package/static/dist/assets/katex-cbdd1ohw.css +1 -0
- package/static/dist/assets/katex-pbij2oo6.js +257 -0
- package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
- package/static/dist/assets/{line-h0ojzsvs.js → line-mtr5k6il.js} +1 -1
- package/static/dist/assets/linear-jlwrvvy6.js +1 -0
- package/static/dist/assets/link-iadw4ghw.js +2 -0
- package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
- package/static/dist/assets/locale-cs-n41hptse.js +5 -0
- package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
- package/static/dist/assets/locale-es-ih078lco.js +5 -0
- package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
- package/static/dist/assets/locale-it-kb96jufy.js +5 -0
- package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
- package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
- package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
- package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
- package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
- package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
- package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
- package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
- package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
- package/static/dist/assets/login-lkhtzfcl.js +1 -0
- package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
- package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
- package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
- package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
- package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
- package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-o1vathns.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
- package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
- package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
- package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
- package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
- package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
- package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
- package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
- package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
- package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
- package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
- package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-owqc4uqn.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
- package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
- package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
- package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
- package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
- package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
- package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
- package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
- package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
- package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
- package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
- package/static/dist/assets/registry-g7w3353u.js +1 -0
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
- package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
- package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
- package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
- package/static/dist/assets/runtime-nlxpnegr.js +1 -0
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-iqc02emv.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
- package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
- package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-i0v116q0.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
- package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
- package/static/dist/assets/share._token-o76ki0ly.js +2 -0
- package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
- package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
- package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
- package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
- package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
- package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
- package/static/dist/assets/src-h89s0hs4.js +1 -0
- package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
- package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
- package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
- package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
- package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
- package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
- package/static/dist/assets/{timeline-definition-24CTP7MA-ol2hnvma.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
- package/static/dist/assets/toast-liparz2j.js +56 -0
- package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
- package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
- package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
- package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
- package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
- package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
- package/static/dist/assets/untitled-mj86r4gz.js +1 -0
- package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
- package/static/dist/assets/useForm-o4uu74mx.js +1 -0
- package/static/dist/assets/{useParams-gjc70qru.js → useParams-ftsveu84.js} +1 -1
- package/static/dist/assets/{useSearch-e6ae539u.js → useSearch-crqueqga.js} +1 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
- package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
- package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
- package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
- package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
- package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
- package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
- package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-aqbgvujg.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
- package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
- package/static/dist/extension-sdk.js +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +180 -136
- package/static/dist/assets/AppShell-cey33v9s.js +0 -8
- package/static/dist/assets/ChatPage-i8wnugr7.js +0 -18
- package/static/dist/assets/ConnectTargets-hg19553x.js +0 -1
- package/static/dist/assets/ExtensionRoute-cly4apm5.js +0 -1
- package/static/dist/assets/Field-j8uqoigz.js +0 -9
- package/static/dist/assets/HubRoute-e4bgz41j.js +0 -2
- package/static/dist/assets/Markdown-lahuichm.js +0 -598
- package/static/dist/assets/Markdown-lwviulmg.css +0 -1
- package/static/dist/assets/RightPanel-lbo4i0fn.js +0 -1
- package/static/dist/assets/Select-gqatm2e0.js +0 -1
- package/static/dist/assets/SessionListPanel-mfqayfjh.js +0 -1
- package/static/dist/assets/SettingsLayout-b8980u7k.js +0 -1
- package/static/dist/assets/States-e7dkgtxf.js +0 -1
- package/static/dist/assets/Toaster-bfqqy786.js +0 -1
- package/static/dist/assets/_app-n1n6pfx1.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-buxjxav3.js +0 -1
- package/static/dist/assets/_app.index-jt8zwiu2.js +0 -1
- package/static/dist/assets/_app.insights-cv76ml71.js +0 -1
- package/static/dist/assets/_app.kanban-h6ts53j7.js +0 -1
- package/static/dist/assets/_app.logs-dm873z90.js +0 -1
- package/static/dist/assets/_app.memory-glk5y6m8.js +0 -1
- package/static/dist/assets/_app.profiles-ka6zpstq.js +0 -1
- package/static/dist/assets/_app.session._sessionId-g2vzghvc.js +0 -1
- package/static/dist/assets/_app.settings-hgjdhzcd.js +0 -1
- package/static/dist/assets/_app.settings._section-jf2kmhae.js +0 -3
- package/static/dist/assets/_app.skills-cii852yq.js +0 -1
- package/static/dist/assets/_app.tasks-fp9q7atm.js +0 -1
- package/static/dist/assets/_app.todos-dw94qzue.js +0 -1
- package/static/dist/assets/_app.workspaces-inyygzze.js +0 -1
- package/static/dist/assets/abnfDiagram-VCTEODGH-gjayq3ra.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-nwb3xovn.js +0 -36
- package/static/dist/assets/archive-b1j004vn.js +0 -1
- package/static/dist/assets/back-g74gkozu.js +0 -1
- package/static/dist/assets/channel-es95o4y8.js +0 -1
- package/static/dist/assets/chunk-DU6HZSFF-gpedttsp.js +0 -128
- package/static/dist/assets/chunk-OSK3NFVY-oy0gwn2n.js +0 -10
- package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +0 -1
- package/static/dist/assets/copied-bli9tbcx.js +0 -1
- package/static/dist/assets/copy-jamtd1pf.js +0 -1
- package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
- package/static/dist/assets/cynefin-OW5HDTMX-f8xfdd3f.js +0 -1
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-n9lb99h9.js +0 -62
- package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
- package/static/dist/assets/dagre-GXQ25YYZ-dgailddi.js +0 -4
- package/static/dist/assets/dagre-lkoknbjn.js +0 -1
- package/static/dist/assets/diagram-UQ7AKVKN-jall3j0x.js +0 -41
- package/static/dist/assets/diagram-VX7I27RA-hendl2tt.js +0 -24
- package/static/dist/assets/diagram-Z3DM3KII-igwc35p3.js +0 -24
- package/static/dist/assets/ebnfDiagram-PWID7BFC-c64knz08.js +0 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-brdu9lcm.js +0 -99
- package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +0 -1
- package/static/dist/assets/folder-plus-lvt9kudb.js +0 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-naa9w6xa.js +0 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-ghoavj8d.js +0 -1
- package/static/dist/assets/host-hzm8n85n.js +0 -1
- package/static/dist/assets/index-drdcaqeq.css +0 -2
- package/static/dist/assets/index-jzm844he.js +0 -11
- package/static/dist/assets/info-A6RAGUB7-mn2ut5ko.js +0 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-h6kuqvsb.js +0 -2
- package/static/dist/assets/lazyRouteComponent-o816ytzl.js +0 -1
- package/static/dist/assets/linear-becwlnhk.js +0 -1
- package/static/dist/assets/link-k01ryaf9.js +0 -1
- package/static/dist/assets/login-bsae49ew.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +0 -1
- package/static/dist/assets/mermaid-parser.core-bnre27u8.js +0 -7
- package/static/dist/assets/not-found-ccnnac39.js +0 -1
- package/static/dist/assets/onboarding-c65tysya.js +0 -1
- package/static/dist/assets/onboarding_model_label-gpn28uix.js +0 -1
- package/static/dist/assets/packet-AYTQ26CC-hatq7m2e.js +0 -1
- package/static/dist/assets/pegDiagram-XKGWAZYB-gwisz8jv.js +0 -1
- package/static/dist/assets/pie-WAS4IAKB-fpftf0mk.js +0 -1
- package/static/dist/assets/pieDiagram-E7YTZNPT-khm1o9a6.js +0 -39
- package/static/dist/assets/radar-RG4KPBEZ-cntfwjpq.js +0 -1
- package/static/dist/assets/railroad-74A4TZTK-a7t5eibp.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +0 -1
- package/static/dist/assets/railroadDiagram-O6MQD6OU-etuh0qa0.js +0 -1
- package/static/dist/assets/refresh-cw-mqbkv2a3.js +0 -1
- package/static/dist/assets/refresh-l29ljp76.js +0 -1
- package/static/dist/assets/requirementDiagram-BXWQKSXE-oq2ulk22.js +0 -84
- package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
- package/static/dist/assets/root-mbklpuv4.js +0 -1
- package/static/dist/assets/saved-ndd4t2xb.js +0 -1
- package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +0 -1
- package/static/dist/assets/share._token-c1ri0hy1.js +0 -2
- package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
- package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +0 -1
- package/static/dist/assets/src-hjyib4mu.js +0 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-dnpt1ato.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +0 -1
- package/static/dist/assets/swimlanes-42K2YHIH-loqbdazb.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +0 -8
- package/static/dist/assets/toast-i3h34v6o.js +0 -64
- package/static/dist/assets/treeView-Q6P3EWNA-idrj6iox.js +0 -1
- package/static/dist/assets/treemap-WGGIJYW6-lzr49vv5.js +0 -1
- package/static/dist/assets/untitled-oaoya4qj.js +0 -1
- package/static/dist/assets/useForm-nfaxgmsk.js +0 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-bvwr9o2z.js +0 -34
- package/static/dist/assets/wardley-WFR3VGLG-31ui8tma.js +0 -1
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-k5wqtx4b.js +0 -78
- /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
|
@@ -19,6 +19,7 @@ import inspect
|
|
|
19
19
|
import json
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
|
+
import queue
|
|
22
23
|
import re
|
|
23
24
|
import threading
|
|
24
25
|
import time
|
|
@@ -26,13 +27,15 @@ import uuid
|
|
|
26
27
|
from collections import OrderedDict
|
|
27
28
|
from functools import lru_cache
|
|
28
29
|
from pathlib import Path
|
|
29
|
-
from typing import Any
|
|
30
|
+
from typing import Any, Callable
|
|
31
|
+
from types import SimpleNamespace
|
|
30
32
|
|
|
31
33
|
from ..errors import InvalidParams, RpcError
|
|
32
34
|
from ..home import profile_home_param, scoped_home, session_workspace
|
|
33
35
|
from ..rpc import CallContext
|
|
34
36
|
from .checkpoints import checkpoint_kwargs
|
|
35
37
|
from .providers import runtime_failure
|
|
38
|
+
from .state_db import release_all, release_home, release_session_db
|
|
36
39
|
from .toolsets import disabled_toolsets
|
|
37
40
|
|
|
38
41
|
log = logging.getLogger("talaria_sidecar.chat")
|
|
@@ -65,6 +68,7 @@ class _Run:
|
|
|
65
68
|
# agent this run binds afterwards forks no review and stays out of the cache.
|
|
66
69
|
self.session_ended = False
|
|
67
70
|
self.home = None # the profile home its agent runs under, set by ``start``
|
|
71
|
+
self.mcp_discovery_outcome = "ok"
|
|
68
72
|
|
|
69
73
|
|
|
70
74
|
class _ClarifyEntry:
|
|
@@ -86,13 +90,22 @@ _RUNS_LOCK = threading.Lock()
|
|
|
86
90
|
# session id -> (agent, signature, profile home).
|
|
87
91
|
_AGENT_CACHE: "OrderedDict[str, tuple[Any, str, Any]]" = OrderedDict()
|
|
88
92
|
_AGENT_CACHE_LOCK = threading.Lock()
|
|
89
|
-
# Running release
|
|
90
|
-
_RELEASES: dict[threading.Thread, Any] = {}
|
|
93
|
+
# Running or queued release work -> the profile home it holds.
|
|
94
|
+
_RELEASES: dict[threading.Thread | _ArchiveCallback, Any] = {}
|
|
91
95
|
_RELEASES_LOCK = threading.Lock()
|
|
92
|
-
|
|
96
|
+
_ARCHIVE_MAX_WORKERS = 4
|
|
97
|
+
_ARCHIVE_QUEUE: queue.Queue[_ArchiveCallback | None] = queue.Queue(maxsize=256)
|
|
98
|
+
_ARCHIVE_WORKERS: list[threading.Thread] = []
|
|
99
|
+
# Non-turn ``(agent, home)`` holders for memory commits, refine, context reads and MCP refresh (under
|
|
100
|
+
# ``_AGENT_CACHE_LOCK``): each defers the agent's release, and deleting ``home`` waits for it.
|
|
93
101
|
_COMMITTING: list = []
|
|
94
102
|
# Set (under ``_RUNS_LOCK``) when shutdown drains: no turn is admitted after the drain's snapshot of ``_RUNS``.
|
|
95
103
|
_DRAINING = threading.Event()
|
|
104
|
+
_MCP_DISCOVERIES: dict[str, threading.Event] = {}
|
|
105
|
+
_MCP_DISCOVERY_LOCK = threading.Lock()
|
|
106
|
+
_MCP_CLOSING_HOMES: set[str] = set()
|
|
107
|
+
_MCP_REDISCOVERY_WAIT = 2.0
|
|
108
|
+
_MCP_PENDING_KEY = "mcp_discovery_pending"
|
|
96
109
|
|
|
97
110
|
|
|
98
111
|
def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
|
|
@@ -111,7 +124,17 @@ def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
|
|
|
111
124
|
|
|
112
125
|
|
|
113
126
|
_RAW_RESULT_MAX_KEYS = 64
|
|
114
|
-
_RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success")
|
|
127
|
+
_RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success", "status", "results")
|
|
128
|
+
# TAL-1261: a ``results`` batch keeps up to 64 items, each projected like the result with 16 fields of 400 characters.
|
|
129
|
+
_RAW_RESULT_ITEM_KEYS = 16
|
|
130
|
+
_RAW_RESULT_ITEM_MAX = 400
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _without_error(item: Any) -> bool:
|
|
134
|
+
"""An item the server's ``presentError`` reads as error-free: not an object, or its ``error`` is blank text, ``false``,
|
|
135
|
+
``null``, a number, or an empty object or list."""
|
|
136
|
+
error = item.get("error") if isinstance(item, dict) else None
|
|
137
|
+
return not (error is True or (isinstance(error, str) and error.strip()) or (isinstance(error, (dict, list)) and error))
|
|
115
138
|
|
|
116
139
|
|
|
117
140
|
def _result_diff(raw: Any) -> str | None:
|
|
@@ -149,10 +172,11 @@ def _edit_metadata(name: str, args: Any, result: Any, snapshot: Any) -> dict:
|
|
|
149
172
|
return {"diff": diff} if diff else {}
|
|
150
173
|
|
|
151
174
|
|
|
152
|
-
def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
|
|
175
|
+
def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX, max_keys: int = _RAW_RESULT_MAX_KEYS, items: bool = True) -> Any:
|
|
153
176
|
"""The tool result as the server's outcome rule reads it, bounded: a dict (or JSON-object text) keeps its first
|
|
154
|
-
``
|
|
155
|
-
|
|
177
|
+
``max_keys`` top-level fields plus its outcome fields, scalars as they are, text and non-empty nested values as capped
|
|
178
|
+
(JSON) text, except a ``results`` list, whose object items keep the same shape one level down (TAL-1261); anything
|
|
179
|
+
else is the capped text. The sidecar decides nothing; the server does."""
|
|
156
180
|
try:
|
|
157
181
|
data = raw if isinstance(raw, dict) else json.loads(str(raw or ""))
|
|
158
182
|
except Exception: # noqa: BLE001
|
|
@@ -161,9 +185,15 @@ def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
|
|
|
161
185
|
return str(raw if raw is not None else "")[:limit]
|
|
162
186
|
out: dict = {}
|
|
163
187
|
# The server's outcome rule reads these, wherever they sit in the result.
|
|
164
|
-
kept = list(data.items())[:
|
|
188
|
+
kept = list(data.items())[:max_keys] + [(k, data[k]) for k in _RAW_RESULT_OUTCOME_KEYS if k in data]
|
|
165
189
|
for key, value in kept:
|
|
166
|
-
if
|
|
190
|
+
if items and key == "results" and isinstance(value, list) and value:
|
|
191
|
+
# Past the cap, the first item without an error rides along, so a long batch is judged like the whole one.
|
|
192
|
+
batch = value[:_RAW_RESULT_MAX_KEYS] + [item for item in value[_RAW_RESULT_MAX_KEYS:] if _without_error(item)][:1]
|
|
193
|
+
# A non-object item stays text, even JSON-object text: only object items count toward the batch's outcome.
|
|
194
|
+
out[key] = [_raw_result(item, _RAW_RESULT_ITEM_MAX, _RAW_RESULT_ITEM_KEYS, False) if isinstance(item, dict)
|
|
195
|
+
else (item if isinstance(item, str) else json.dumps(item, default=str))[:_RAW_RESULT_ITEM_MAX] for item in batch]
|
|
196
|
+
elif value is None or isinstance(value, (bool, int)) or (isinstance(value, float) and value == value and abs(value) != float("inf")):
|
|
167
197
|
out[str(key)] = value
|
|
168
198
|
elif isinstance(value, (dict, list)) and not value:
|
|
169
199
|
out[str(key)] = {} if isinstance(value, dict) else []
|
|
@@ -726,56 +756,293 @@ def _turn_identity(session_id: str, workspace: str):
|
|
|
726
756
|
_LEGACY_TOOLSET_ALIASES = {"hermes": ("hermes-cli", "hermes-api-server")}
|
|
727
757
|
|
|
728
758
|
|
|
729
|
-
def _turn_toolsets(toolsets) -> list[str]:
|
|
759
|
+
def _turn_toolsets(toolsets, cfg: dict | None = None) -> list[str]:
|
|
730
760
|
"""The session's override as sent, else the profile's configured toolsets. ``None`` would hand the Agent every
|
|
731
761
|
registered toolset."""
|
|
732
|
-
return toolsets if isinstance(toolsets, list) else _profile_toolsets()
|
|
762
|
+
return toolsets if isinstance(toolsets, list) else _profile_toolsets(cfg)
|
|
733
763
|
|
|
734
764
|
|
|
735
|
-
def _profile_toolsets() -> list[str]:
|
|
765
|
+
def _profile_toolsets(cfg: dict | None = None) -> list[str]:
|
|
736
766
|
"""The profile's ``platform_toolsets.cli`` through the Agent's own resolver (default-off toolsets stay off), like
|
|
737
767
|
``hermes chat``. Runs under the call's ``scoped_home``."""
|
|
738
768
|
from hermes_cli.config import load_config
|
|
739
769
|
from hermes_cli.tools_config import _get_platform_tools
|
|
740
770
|
|
|
741
|
-
resolved = sorted(_get_platform_tools(load_config() or {}, "cli"))
|
|
771
|
+
resolved = sorted(_get_platform_tools((load_config() or {}) if cfg is None else cfg, "cli"))
|
|
742
772
|
return list(dict.fromkeys(name for raw in resolved for name in _LEGACY_TOOLSET_ALIASES.get(raw, (raw,))))
|
|
743
773
|
|
|
744
774
|
|
|
745
|
-
def _discover_mcp_tools(run: _Run) -> None:
|
|
746
|
-
"""
|
|
747
|
-
|
|
748
|
-
``scoped_home``; a failure leaves the turn without MCP tools rather than failing it. A Stop does not wait for a slow
|
|
749
|
-
server: the turn moves on to its cancelled end while the connect finishes in the background."""
|
|
750
|
-
from .commands import discover_profile_mcp_tools
|
|
775
|
+
def _discover_mcp_tools(run: _Run) -> threading.Event | None:
|
|
776
|
+
"""Wait for the profile's first discovery; later turns give a shared background retry at most two seconds."""
|
|
777
|
+
from .commands import discover_profile_mcp_tools, profile_mcp_tools_discovered
|
|
751
778
|
|
|
752
|
-
|
|
779
|
+
profile_home = run.home
|
|
780
|
+
home = os.path.realpath(profile_home)
|
|
781
|
+
deadline = time.monotonic() + _MCP_REDISCOVERY_WAIT if profile_mcp_tools_discovered(home) else None
|
|
753
782
|
|
|
754
783
|
def discover() -> None:
|
|
755
784
|
try:
|
|
756
|
-
discover_profile_mcp_tools()
|
|
785
|
+
discovered = discover_profile_mcp_tools()
|
|
786
|
+
refresh_profile_agents_mcp_tools(profile_home)
|
|
787
|
+
done.startup_outcome = "ok" if discovered else "noop"
|
|
757
788
|
except Exception: # noqa: BLE001
|
|
789
|
+
done.startup_outcome = "error"
|
|
758
790
|
log.warning("MCP discovery failed", exc_info=True)
|
|
759
791
|
finally:
|
|
792
|
+
with _RELEASES_LOCK:
|
|
793
|
+
_RELEASES.pop(worker, None)
|
|
760
794
|
done.set()
|
|
761
795
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
796
|
+
with _MCP_DISCOVERY_LOCK:
|
|
797
|
+
if _DRAINING.is_set():
|
|
798
|
+
raise RpcError("The sidecar is shutting down.", condition="sidecar_unavailable")
|
|
799
|
+
if home in _MCP_CLOSING_HOMES:
|
|
800
|
+
raise RpcError("The profile is being deleted.", condition="profile_unavailable")
|
|
801
|
+
done = _MCP_DISCOVERIES.get(home)
|
|
802
|
+
if done is None or done.is_set():
|
|
803
|
+
done = threading.Event()
|
|
804
|
+
done.startup_outcome = "unknown"
|
|
805
|
+
_MCP_DISCOVERIES[home] = done
|
|
806
|
+
# The copied context carries the turn's home override and secret scope into the worker.
|
|
807
|
+
worker = threading.Thread(target=contextvars.copy_context().run, args=(discover,), daemon=True, name=f"chat-mcp-{run.stream_id[:8]}")
|
|
808
|
+
worker.failed = False
|
|
809
|
+
# Existing profile/shutdown custody includes this worker after its originating turn has ended.
|
|
810
|
+
with _RELEASES_LOCK:
|
|
811
|
+
_RELEASES[worker] = profile_home
|
|
812
|
+
try:
|
|
813
|
+
worker.start()
|
|
814
|
+
except Exception:
|
|
815
|
+
with _RELEASES_LOCK:
|
|
816
|
+
_RELEASES.pop(worker, None)
|
|
817
|
+
done.set()
|
|
818
|
+
raise
|
|
819
|
+
while not done.is_set():
|
|
765
820
|
if run.cancel.is_set() or run.ctx.cancelled:
|
|
821
|
+
run.mcp_discovery_outcome = "cancelled"
|
|
822
|
+
return None
|
|
823
|
+
remaining = None if deadline is None else deadline - time.monotonic()
|
|
824
|
+
if remaining is not None and remaining <= 0:
|
|
825
|
+
run.mcp_discovery_outcome = "timeout"
|
|
826
|
+
run.ctx.emit("notice", {"key": _MCP_PENDING_KEY, "level": "info", "kind": "sticky",
|
|
827
|
+
"text": "Some tools are still connecting. Using available tools for this turn.",
|
|
828
|
+
"lapses_with_agent": False})
|
|
829
|
+
return done
|
|
830
|
+
done.wait(0.25 if remaining is None else min(0.25, remaining))
|
|
831
|
+
run.mcp_discovery_outcome = getattr(done, "startup_outcome", "ok")
|
|
832
|
+
return None
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
# Serializes every hook change the sidecar makes, so two turns or a turn and a profile deletion never interleave a
|
|
836
|
+
# drop with a registration.
|
|
837
|
+
_HOOKS_LOCK = threading.Lock()
|
|
838
|
+
_HOOK_CALLBACK_PREFIXES = {"agent.shell_hooks": "shell_hook[", "agent.outbound_webhooks": "outbound_webhook["}
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _drop_home_callbacks(*modules) -> None:
|
|
842
|
+
"""Forget this home's registrations in each Agent hook module and remove their callbacks from the home's plugin
|
|
843
|
+
manager; the Agent has no teardown for either registry. Runs under the call's ``scoped_home`` and ``_HOOKS_LOCK``."""
|
|
844
|
+
from agent import shell_hooks
|
|
845
|
+
from hermes_cli.plugins import get_plugin_manager
|
|
846
|
+
|
|
847
|
+
prefixes = tuple(_HOOK_CALLBACK_PREFIXES[module.__name__] for module in modules)
|
|
848
|
+
for module in modules:
|
|
849
|
+
shell_hooks._forget_home_registrations(module._registered, module._registered_lock)
|
|
850
|
+
manager = get_plugin_manager()
|
|
851
|
+
with shell_hooks._registered_lock:
|
|
852
|
+
for event, callbacks in list(manager._hooks.items()):
|
|
853
|
+
# Replaced, never edited in place: a concurrent invoke_hook keeps iterating the old list.
|
|
854
|
+
manager._hooks[event] = [cb for cb in callbacks if not getattr(cb, "__name__", "").startswith(prefixes)]
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def _hook_state_trusted(shell_hooks, cfg: dict) -> bool:
|
|
858
|
+
"""``sync_from_config``'s removal guard for Agents without it: an empty (failed-open) config, an unreadable
|
|
859
|
+
allowlist or a ``hooks:`` entry the Agent could not parse must never disarm a registered guard."""
|
|
860
|
+
if not cfg:
|
|
861
|
+
return False
|
|
862
|
+
try:
|
|
863
|
+
allowlist = json.loads(shell_hooks.allowlist_path().read_text(encoding="utf-8-sig"))
|
|
864
|
+
except FileNotFoundError:
|
|
865
|
+
allowlist = {"approvals": []}
|
|
866
|
+
except (OSError, ValueError):
|
|
867
|
+
return False
|
|
868
|
+
if not isinstance(allowlist, dict) or not isinstance(allowlist.get("approvals"), list):
|
|
869
|
+
return False
|
|
870
|
+
hooks = cfg.get("hooks")
|
|
871
|
+
if hooks is None:
|
|
872
|
+
return True
|
|
873
|
+
if not isinstance(hooks, dict):
|
|
874
|
+
return False
|
|
875
|
+
events = [entries for event, entries in hooks.items() if event not in ("output_spill", "outbound") and entries is not None]
|
|
876
|
+
if not all(isinstance(entries, list) for entries in events):
|
|
877
|
+
return False
|
|
878
|
+
return len(shell_hooks.iter_configured_hooks(cfg)) == sum(len(entries) for entries in events)
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def _replace_shell_hooks(shell_hooks, cfg: dict) -> None:
|
|
882
|
+
"""Agents without ``sync_from_config``: wire the home's consented hooks from ``cfg`` and drop the rest, so a hook
|
|
883
|
+
removed from config or revoked stops, as it does on v0.22.3. The stale callbacks go only after the current set is
|
|
884
|
+
wired, so a concurrent turn's tool call always meets its guard. Add-only when the hook state is not trusted."""
|
|
885
|
+
if not _hook_state_trusted(shell_hooks, cfg):
|
|
886
|
+
shell_hooks.register_from_config(cfg, accept_hooks=False)
|
|
887
|
+
return
|
|
888
|
+
from hermes_cli.plugins import get_plugin_manager
|
|
889
|
+
|
|
890
|
+
manager = get_plugin_manager()
|
|
891
|
+
stale = [cb for callbacks in manager._hooks.values() for cb in callbacks if getattr(cb, "__name__", "").startswith("shell_hook[")]
|
|
892
|
+
shell_hooks._forget_home_registrations(shell_hooks._registered, shell_hooks._registered_lock)
|
|
893
|
+
shell_hooks.register_from_config(cfg, accept_hooks=False)
|
|
894
|
+
with shell_hooks._registered_lock:
|
|
895
|
+
for event, callbacks in list(manager._hooks.items()):
|
|
896
|
+
manager._hooks[event] = [cb for cb in callbacks if not any(cb is old for old in stale)]
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _register_profile_hooks(home: Path) -> None:
|
|
900
|
+
"""Wire the profile's consented shell hooks and current outbound webhooks before the turn's tools run, like the
|
|
901
|
+
gateway on every session open (TAL-1258). Hooks added or approved since the last call join this one; hooks removed
|
|
902
|
+
from config or revoked stop, through ``sync_from_config`` or its sidecar twin for older Agents. Outbound webhooks
|
|
903
|
+
are add-only on every Agent, so each call replaces the home's set with the configured one (a concurrent turn's
|
|
904
|
+
notification can miss that instant; webhooks only observe). Consent comes from the allowlist or an opt-in in
|
|
905
|
+
force: stdin is the RPC pipe, so nothing prompts. A failure is logged and never blocks the turn."""
|
|
906
|
+
try:
|
|
907
|
+
from agent import outbound_webhooks, shell_hooks
|
|
908
|
+
from hermes_cli.config import load_config
|
|
909
|
+
|
|
910
|
+
with _HOOKS_LOCK:
|
|
911
|
+
sync = getattr(shell_hooks, "sync_from_config", None)
|
|
912
|
+
if sync is not None:
|
|
913
|
+
sync(load_config)
|
|
914
|
+
else:
|
|
915
|
+
_replace_shell_hooks(shell_hooks, load_config())
|
|
916
|
+
_drop_home_callbacks(outbound_webhooks)
|
|
917
|
+
outbound_webhooks.register_from_config(load_config())
|
|
918
|
+
except Exception: # noqa: BLE001
|
|
919
|
+
log.warning("shell-hook sync failed; keeping the registered hooks", exc_info=True)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def forget_profile_hooks(home: Path) -> None:
|
|
923
|
+
"""Profile deletion: drop the home's shell hooks and outbound webhooks. The Agent keys both registries and its
|
|
924
|
+
plugin manager by path, so a profile recreated at that path would otherwise fire hooks its new config and allowlist
|
|
925
|
+
never approved. Raises rather than let the profile go with its hooks still wired."""
|
|
926
|
+
try:
|
|
927
|
+
from agent import outbound_webhooks, shell_hooks
|
|
928
|
+
except ImportError: # no Agent, so no hook was ever registered
|
|
929
|
+
return
|
|
930
|
+
with _HOOKS_LOCK, scoped_home(home):
|
|
931
|
+
_drop_home_callbacks(shell_hooks, outbound_webhooks)
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
@contextlib.contextmanager
|
|
935
|
+
def agent_scope(home: Path):
|
|
936
|
+
"""``scoped_home`` for a call that runs Agent work: the profile's hooks are wired before any of it (TAL-1258), so
|
|
937
|
+
the call's first LLM request, tool call or lifecycle event already sees them, even as a session's first work after
|
|
938
|
+
a sidecar restart."""
|
|
939
|
+
with scoped_home(home):
|
|
940
|
+
_register_profile_hooks(home)
|
|
941
|
+
yield
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class _ArchiveCallback:
|
|
945
|
+
"""Queued work shares the release threads' join/liveness contract without owning an OS thread."""
|
|
946
|
+
name = "session-archive"
|
|
947
|
+
failed = False
|
|
948
|
+
|
|
949
|
+
def __init__(self, callback: Callable[[], None]):
|
|
950
|
+
self.callback = callback
|
|
951
|
+
self.done = threading.Event()
|
|
952
|
+
|
|
953
|
+
def run(self) -> None:
|
|
954
|
+
try:
|
|
955
|
+
self.callback()
|
|
956
|
+
finally:
|
|
957
|
+
self.done.set()
|
|
958
|
+
|
|
959
|
+
def join(self, timeout=None) -> None:
|
|
960
|
+
self.done.wait(timeout)
|
|
961
|
+
|
|
962
|
+
def is_alive(self) -> bool:
|
|
963
|
+
return not self.done.is_set()
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
def _archive_worker(pending) -> None:
|
|
967
|
+
while True:
|
|
968
|
+
work = pending.get()
|
|
969
|
+
try:
|
|
970
|
+
if work is None:
|
|
971
|
+
return
|
|
972
|
+
try:
|
|
973
|
+
work.run()
|
|
974
|
+
except BaseException: # a plugin must not kill a shared worker
|
|
975
|
+
log.warning("Session archive notification worker failed")
|
|
976
|
+
finally:
|
|
977
|
+
pending.task_done()
|
|
978
|
+
del work
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def _schedule_archive(work: _ArchiveCallback, home: Path) -> None:
|
|
982
|
+
# Admission shares the profile-deletion/shutdown fence; queued work uses the existing release custody roster.
|
|
983
|
+
with _MCP_DISCOVERY_LOCK:
|
|
984
|
+
if _DRAINING.is_set() or os.path.realpath(home) in _MCP_CLOSING_HOMES:
|
|
985
|
+
log.warning("Session archive notification unavailable during profile or sidecar shutdown")
|
|
766
986
|
return
|
|
987
|
+
if len(_ARCHIVE_WORKERS) < _ARCHIVE_MAX_WORKERS:
|
|
988
|
+
thread = threading.Thread(target=_archive_worker, args=(_ARCHIVE_QUEUE,), name="session-archive-worker", daemon=True)
|
|
989
|
+
try:
|
|
990
|
+
thread.start()
|
|
991
|
+
except RuntimeError:
|
|
992
|
+
log.warning("Session archive notification worker could not start")
|
|
993
|
+
if not _ARCHIVE_WORKERS:
|
|
994
|
+
return
|
|
995
|
+
else:
|
|
996
|
+
_ARCHIVE_WORKERS.append(thread)
|
|
997
|
+
with _RELEASES_LOCK:
|
|
998
|
+
_RELEASES[work] = home
|
|
999
|
+
try:
|
|
1000
|
+
_ARCHIVE_QUEUE.put_nowait(work)
|
|
1001
|
+
except queue.Full:
|
|
1002
|
+
_RELEASES.pop(work, None)
|
|
1003
|
+
log.warning("Session archive notification queue is full")
|
|
767
1004
|
|
|
768
1005
|
|
|
769
|
-
def
|
|
1006
|
+
def _notify_archive(home: Path, session_id: str, lineage: list[str] | None) -> None:
|
|
1007
|
+
import hermes_state_common
|
|
1008
|
+
|
|
1009
|
+
emit = getattr(hermes_state_common, "emit_session_deleted", None)
|
|
1010
|
+
if emit is None:
|
|
1011
|
+
return
|
|
1012
|
+
# Upstream hook warnings can include arbitrary callback exceptions. Keep one payload-free diagnostic.
|
|
1013
|
+
thread_id, warned = threading.get_ident(), False
|
|
1014
|
+
|
|
1015
|
+
def bounded_diagnostic(record):
|
|
1016
|
+
nonlocal warned
|
|
1017
|
+
if record.thread != thread_id:
|
|
1018
|
+
return True
|
|
1019
|
+
warned = warned or record.levelno >= logging.WARNING
|
|
1020
|
+
return False
|
|
1021
|
+
|
|
1022
|
+
handlers = list(logging.getLogger().handlers)
|
|
1023
|
+
for handler in handlers:
|
|
1024
|
+
handler.addFilter(bounded_diagnostic)
|
|
1025
|
+
try:
|
|
1026
|
+
# The emitter only needs db_path for profile identity. Opening SessionDB would migrate an owner's store.
|
|
1027
|
+
emit(SimpleNamespace(db_path=home / "state.db"), [session_id, *(lineage or [])],
|
|
1028
|
+
reason="archive", primary=session_id, sessions_dir=home / "sessions")
|
|
1029
|
+
finally:
|
|
1030
|
+
for handler in handlers:
|
|
1031
|
+
handler.removeFilter(bounded_diagnostic)
|
|
1032
|
+
if warned:
|
|
1033
|
+
log.warning("Session archive notification reported a callback failure")
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _profile_fallback_chain(cfg: dict | None = None) -> list[dict] | None:
|
|
770
1037
|
"""The profile's fallback routes (``fallback_providers`` first, then legacy ``fallback_model``, deduplicated)
|
|
771
1038
|
through the Agent's own resolver, like the CLI and gateway. Runs under the call's ``scoped_home``."""
|
|
772
1039
|
from hermes_cli.config import load_config
|
|
773
1040
|
from hermes_cli.fallback_config import get_fallback_chain
|
|
774
1041
|
|
|
775
|
-
return get_fallback_chain(load_config() or {}) or None
|
|
1042
|
+
return copy.deepcopy(get_fallback_chain((load_config() or {}) if cfg is None else cfg)) or None
|
|
776
1043
|
|
|
777
1044
|
|
|
778
|
-
def _profile_prefill_messages() -> list[dict] | None:
|
|
1045
|
+
def _profile_prefill_messages(cfg: dict | None = None) -> list[dict] | None:
|
|
779
1046
|
"""The profile's few-shot prefill (``HERMES_PREFILL_MESSAGES_FILE``, then ``prefill_messages_file``, then legacy
|
|
780
1047
|
``agent.prefill_messages_file``; relative to the profile) through the Agent's own loader, like the CLI, gateway and
|
|
781
1048
|
Desktop. A missing or empty file is None. Runs under the call's ``scoped_home``."""
|
|
@@ -783,15 +1050,15 @@ def _profile_prefill_messages() -> list[dict] | None:
|
|
|
783
1050
|
from hermes_cli.config import load_config
|
|
784
1051
|
from hermes_constants import get_hermes_home
|
|
785
1052
|
|
|
786
|
-
return _load_prefill_messages(_resolve_prefill_messages_file(load_config() or {}), get_hermes_home()) or None
|
|
1053
|
+
return _load_prefill_messages(_resolve_prefill_messages_file((load_config() or {}) if cfg is None else cfg), get_hermes_home()) or None
|
|
787
1054
|
|
|
788
1055
|
|
|
789
|
-
def _main_model_request_overrides(model: str, provider, base_url: str = "") -> dict | None:
|
|
1056
|
+
def _main_model_request_overrides(model: str, provider, base_url: str = "", cfg: dict | None = None) -> dict | None:
|
|
790
1057
|
"""Predecessor ``_main_model_request_overrides``: the profile's ``model.service_tier`` and ``model.extra_body``
|
|
791
1058
|
(written by ``/api/model/set``) apply only to turns on that configured main model. Runs under ``scoped_home``."""
|
|
792
1059
|
from hermes_cli.config import load_config
|
|
793
1060
|
|
|
794
|
-
model_cfg = (load_config() or {}).get("model")
|
|
1061
|
+
model_cfg = ((load_config() or {}) if cfg is None else cfg).get("model")
|
|
795
1062
|
if not isinstance(model_cfg, dict):
|
|
796
1063
|
return None
|
|
797
1064
|
main_model = str(model_cfg.get("default") or model_cfg.get("name") or "").strip()
|
|
@@ -808,15 +1075,15 @@ def _main_model_request_overrides(model: str, provider, base_url: str = "") -> d
|
|
|
808
1075
|
overrides = fast_overrides(model, str(provider or ""), base_url, str(model_cfg.get("service_tier") or "").strip().lower()) or {}
|
|
809
1076
|
extra_body = model_cfg.get("extra_body")
|
|
810
1077
|
if isinstance(extra_body, dict) and extra_body:
|
|
811
|
-
overrides["extra_body"] = extra_body
|
|
1078
|
+
overrides["extra_body"] = copy.deepcopy(extra_body)
|
|
812
1079
|
return overrides or None
|
|
813
1080
|
|
|
814
1081
|
|
|
815
|
-
def _request_overrides(model: str, provider, runtime: dict) -> dict | None:
|
|
1082
|
+
def _request_overrides(model: str, provider, runtime: dict, cfg: dict | None = None) -> dict | None:
|
|
816
1083
|
"""The resolver's ``request_overrides`` (a custom provider entry's ``extra_body``) under the main-model ones, whose
|
|
817
1084
|
``extra_body`` wins key by key, the Agent's own merge order. Runs under ``scoped_home``."""
|
|
818
1085
|
merged = dict(runtime.get("request_overrides") or {})
|
|
819
|
-
main = _main_model_request_overrides(model, provider, str(runtime.get("base_url") or "")) or {}
|
|
1086
|
+
main = _main_model_request_overrides(model, provider, str(runtime.get("base_url") or ""), cfg) or {}
|
|
820
1087
|
extra_body = {**(merged.get("extra_body") or {}), **(main.get("extra_body") or {})}
|
|
821
1088
|
merged.update(main)
|
|
822
1089
|
if extra_body:
|
|
@@ -824,6 +1091,31 @@ def _request_overrides(model: str, provider, runtime: dict) -> dict | None:
|
|
|
824
1091
|
return merged or None
|
|
825
1092
|
|
|
826
1093
|
|
|
1094
|
+
def _provider_routing_kwargs(cfg: dict | None = None) -> dict:
|
|
1095
|
+
"""The profile's OpenRouter ``provider_routing`` and ``openrouter.min_coding_score`` as ``AIAgent`` kwargs, with the
|
|
1096
|
+
Agent's own mapping (``gateway/session_worker_construct.routing_agent_kwargs``). The score is coerced like the CLI's
|
|
1097
|
+
(``cli_init_mixin``), so a quoted number counts and a value outside 0..1 stays unset. Runs under ``scoped_home``."""
|
|
1098
|
+
from hermes_cli.config import load_config
|
|
1099
|
+
|
|
1100
|
+
if cfg is None:
|
|
1101
|
+
cfg = load_config() or {}
|
|
1102
|
+
routing = cfg.get("provider_routing")
|
|
1103
|
+
routing = routing if isinstance(routing, dict) else {}
|
|
1104
|
+
openrouter = cfg.get("openrouter")
|
|
1105
|
+
raw_score = openrouter.get("min_coding_score") if isinstance(openrouter, dict) else None
|
|
1106
|
+
try:
|
|
1107
|
+
score = float(raw_score) if raw_score not in (None, "") else None
|
|
1108
|
+
except (TypeError, ValueError):
|
|
1109
|
+
score = None
|
|
1110
|
+
return copy.deepcopy({
|
|
1111
|
+
"providers_allowed": routing.get("only"), "providers_ignored": routing.get("ignore"),
|
|
1112
|
+
"providers_order": routing.get("order"), "provider_sort": routing.get("sort"),
|
|
1113
|
+
"provider_require_parameters": routing.get("require_parameters", False),
|
|
1114
|
+
"provider_data_collection": routing.get("data_collection"),
|
|
1115
|
+
"openrouter_min_coding_score": score if score is not None and 0.0 <= score <= 1.0 else None,
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
|
|
827
1119
|
def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, kwargs: dict) -> str:
|
|
828
1120
|
"""Cache identity of an ``AIAgent``: everything its constructor bound from the resolved runtime, so a rotated key,
|
|
829
1121
|
a different API mode, ACP command, or credential pool never reuses an agent built for the old bundle. The key
|
|
@@ -841,6 +1133,9 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
|
|
|
841
1133
|
# Bound at construction too: a reasoning-effort change from the composer must build a fresh agent.
|
|
842
1134
|
"reasoning_config": kwargs.get("reasoning_config"),
|
|
843
1135
|
"request_overrides": kwargs.get("request_overrides"),
|
|
1136
|
+
"provider_routing": [kwargs.get(name) for name in (
|
|
1137
|
+
"providers_allowed", "providers_ignored", "providers_order", "provider_sort", "provider_require_parameters",
|
|
1138
|
+
"provider_data_collection", "openrouter_min_coding_score")],
|
|
844
1139
|
"checkpoints": [kwargs.get(name) for name in ("checkpoints_enabled", "checkpoint_max_snapshots", "checkpoint_max_total_size_mb", "checkpoint_max_file_size_mb")],
|
|
845
1140
|
# Entries may carry their own ``api_key``.
|
|
846
1141
|
"fallback_model": hashlib.sha256(json.dumps(kwargs.get("fallback_model"), sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16],
|
|
@@ -850,7 +1145,7 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
|
|
|
850
1145
|
return json.dumps(bundle, sort_keys=True, default=str)
|
|
851
1146
|
|
|
852
1147
|
|
|
853
|
-
def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider, runtime: dict, toolsets) -> dict:
|
|
1148
|
+
def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider, runtime: dict, toolsets, cfg: dict | None = None) -> dict:
|
|
854
1149
|
"""The ``AIAgent`` arguments of a session's agent apart from a turn's callbacks: the resolved runtime, the session's
|
|
855
1150
|
toolsets and budgets, and the profile's fallback chain, request overrides, prefill and checkpoints. Everything
|
|
856
1151
|
``_agent_signature`` reads is here, so an agent ``/refine`` builds (TAL-902) is the one the next turn reuses. Runs under
|
|
@@ -863,7 +1158,7 @@ def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider,
|
|
|
863
1158
|
platform="webui",
|
|
864
1159
|
quiet_mode=True,
|
|
865
1160
|
enabled_toolsets=toolsets,
|
|
866
|
-
disabled_toolsets=disabled_toolsets(),
|
|
1161
|
+
disabled_toolsets=disabled_toolsets(cfg),
|
|
867
1162
|
session_id=session_id,
|
|
868
1163
|
)
|
|
869
1164
|
for name, value in (
|
|
@@ -886,17 +1181,21 @@ def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider,
|
|
|
886
1181
|
reasoning_config = params.get("reasoning_config")
|
|
887
1182
|
if isinstance(reasoning_config, dict) and _supported(AIAgent, "reasoning_config"):
|
|
888
1183
|
kwargs["reasoning_config"] = reasoning_config
|
|
889
|
-
fallback_chain = _profile_fallback_chain()
|
|
1184
|
+
fallback_chain = _profile_fallback_chain(cfg)
|
|
890
1185
|
if fallback_chain and _supported(AIAgent, "fallback_model"):
|
|
891
1186
|
kwargs["fallback_model"] = fallback_chain
|
|
892
|
-
request_overrides = _request_overrides(model, provider, runtime)
|
|
1187
|
+
request_overrides = _request_overrides(model, provider, runtime, cfg)
|
|
893
1188
|
if request_overrides and _supported(AIAgent, "request_overrides"):
|
|
894
1189
|
kwargs["request_overrides"] = request_overrides
|
|
895
|
-
prefill_messages = _profile_prefill_messages()
|
|
1190
|
+
prefill_messages = _profile_prefill_messages(cfg)
|
|
896
1191
|
if prefill_messages and _supported(AIAgent, "prefill_messages"):
|
|
897
1192
|
kwargs["prefill_messages"] = prefill_messages
|
|
1193
|
+
# TAL-976: the profile's OpenRouter provider routing, as the Agent's own CLI and gateway turns read it.
|
|
1194
|
+
for name, value in _provider_routing_kwargs(cfg).items():
|
|
1195
|
+
if _supported(AIAgent, name):
|
|
1196
|
+
kwargs[name] = value
|
|
898
1197
|
# TAL-913: the profile's ``checkpoints`` section, as the Agent's own CLI and gateway turns read it.
|
|
899
|
-
for name, value in checkpoint_kwargs().items():
|
|
1198
|
+
for name, value in checkpoint_kwargs(cfg).items():
|
|
900
1199
|
if _supported(AIAgent, name):
|
|
901
1200
|
kwargs[name] = value
|
|
902
1201
|
return kwargs
|
|
@@ -911,7 +1210,7 @@ def _build_agent(AIAgent, kwargs: dict, home, session_id: str, state_db: bool =
|
|
|
911
1210
|
try:
|
|
912
1211
|
agent = AIAgent(**kwargs)
|
|
913
1212
|
except BaseException:
|
|
914
|
-
|
|
1213
|
+
release_session_db(session_db)
|
|
915
1214
|
raise
|
|
916
1215
|
agent._talaria_session_db = session_db # released with the agent (``_release_agent``)
|
|
917
1216
|
return agent
|
|
@@ -963,7 +1262,7 @@ def _release_agent(agent, home) -> threading.Thread:
|
|
|
963
1262
|
if not released:
|
|
964
1263
|
# The memory flush needs the profile; the LLM clients and the Codex child close regardless.
|
|
965
1264
|
call("release_clients")
|
|
966
|
-
|
|
1265
|
+
release_session_db(getattr(agent, "_talaria_session_db", None))
|
|
967
1266
|
with _RELEASES_LOCK:
|
|
968
1267
|
_RELEASES.pop(thread, None)
|
|
969
1268
|
|
|
@@ -979,7 +1278,7 @@ def _release_agents_locked(entries) -> list[threading.Thread]:
|
|
|
979
1278
|
"""Release cache entries that just left the cache (caller holds ``_AGENT_CACHE_LOCK``). An agent a turn still holds
|
|
980
1279
|
keeps its memory provider and clients; that turn releases it when it ends."""
|
|
981
1280
|
with _RUNS_LOCK:
|
|
982
|
-
held = [run.agent for run in _RUNS.values()] + _COMMITTING
|
|
1281
|
+
held = [run.agent for run in _RUNS.values()] + [agent for agent, _home in _COMMITTING]
|
|
983
1282
|
return [_release_agent(agent, home) for agent, _signature, home in entries if not any(agent is other for other in held)]
|
|
984
1283
|
|
|
985
1284
|
|
|
@@ -1075,7 +1374,7 @@ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
|
|
|
1075
1374
|
"""Shutdown: release every cached agent, stop running turns (like a Stop) so each releases the agent it holds, and
|
|
1076
1375
|
wait, bounded, for those turns and every release still running."""
|
|
1077
1376
|
deadline = time.monotonic() + timeout
|
|
1078
|
-
with _AGENT_CACHE_LOCK, _RUNS_LOCK:
|
|
1377
|
+
with _MCP_DISCOVERY_LOCK, _AGENT_CACHE_LOCK, _RUNS_LOCK:
|
|
1079
1378
|
# Admission closes with the snapshot, so no later turn forks a review outside it.
|
|
1080
1379
|
_DRAINING.set()
|
|
1081
1380
|
runs = list(_RUNS.values())
|
|
@@ -1096,6 +1395,8 @@ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
|
|
|
1096
1395
|
with _RELEASES_LOCK:
|
|
1097
1396
|
threads = list(_RELEASES)
|
|
1098
1397
|
_join(threads, deadline)
|
|
1398
|
+
# TAL-1191: the state.db handles state_db writes keep between turns; each final release checkpoints once.
|
|
1399
|
+
release_all()
|
|
1099
1400
|
|
|
1100
1401
|
|
|
1101
1402
|
def refresh_profile_agents_mcp_tools(home) -> None:
|
|
@@ -1108,34 +1409,69 @@ def refresh_profile_agents_mcp_tools(home) -> None:
|
|
|
1108
1409
|
|
|
1109
1410
|
target = os.path.realpath(home)
|
|
1110
1411
|
with _AGENT_CACHE_LOCK:
|
|
1111
|
-
|
|
1112
|
-
for
|
|
1113
|
-
|
|
1114
|
-
|
|
1412
|
+
entries = [(sid, entry) for sid, entry in _AGENT_CACHE.items() if os.path.realpath(entry[2]) == target]
|
|
1413
|
+
_COMMITTING.extend((entry[0], entry[2]) for _sid, entry in entries)
|
|
1414
|
+
try:
|
|
1415
|
+
for session_id, (agent, _signature, _home) in entries:
|
|
1115
1416
|
try:
|
|
1116
|
-
refresh_agent_mcp_tools(
|
|
1417
|
+
refresh_agent_mcp_tools(agent, quiet_mode=True, content_aware=True)
|
|
1117
1418
|
except Exception: # noqa: BLE001
|
|
1118
|
-
log.warning("Failed to refresh MCP tools for session %s
|
|
1119
|
-
|
|
1120
|
-
|
|
1419
|
+
log.warning("Failed to refresh MCP tools for session %s", session_id, exc_info=True)
|
|
1420
|
+
with _AGENT_CACHE_LOCK:
|
|
1421
|
+
cached = _AGENT_CACHE.get(session_id)
|
|
1422
|
+
# A turn may have replaced or evicted this agent while its refresh ran.
|
|
1423
|
+
if cached is not None and cached[0] is agent:
|
|
1424
|
+
_AGENT_CACHE.pop(session_id)
|
|
1425
|
+
finally:
|
|
1426
|
+
with _AGENT_CACHE_LOCK:
|
|
1427
|
+
for _sid, (agent, _signature, owner) in entries:
|
|
1428
|
+
_COMMITTING.remove((agent, owner))
|
|
1429
|
+
_release_if_dropped_locked(agent, owner)
|
|
1430
|
+
|
|
1121
1431
|
|
|
1432
|
+
@contextlib.contextmanager
|
|
1433
|
+
def release_profile_agents(home):
|
|
1434
|
+
"""Fence discovery and drain this profile's agents and background work before the caller removes its home."""
|
|
1435
|
+
from .commands import forget_profile_mcp_tools
|
|
1122
1436
|
|
|
1123
|
-
def release_profile_agents(home) -> None:
|
|
1124
|
-
"""Profile deletion: release the profile's cached agents, cancel their background reviews, and wait for both and
|
|
1125
|
-
every release running under its home. Fails rather than let the home go while a memory flush still runs under it."""
|
|
1126
1437
|
target = os.path.realpath(home)
|
|
1438
|
+
with _MCP_DISCOVERY_LOCK:
|
|
1439
|
+
if target in _MCP_CLOSING_HOMES:
|
|
1440
|
+
raise RpcError("The profile is already being deleted.", condition="profile_unavailable")
|
|
1441
|
+
_MCP_CLOSING_HOMES.add(target)
|
|
1442
|
+
try:
|
|
1443
|
+
with _AGENT_CACHE_LOCK:
|
|
1444
|
+
entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
|
|
1445
|
+
started = _release_agents_locked(entries)
|
|
1446
|
+
started += _cancel_background_reviews([(entry[0], entry[2]) for entry in entries], "profile deleted")
|
|
1447
|
+
deadline = time.monotonic() + _PROFILE_RELEASE_TIMEOUT
|
|
1448
|
+
# Non-turn holders keep their agent past the cache; its release starts when the last holder lets go.
|
|
1449
|
+
while (holding := _holding_home(target)) and time.monotonic() < deadline:
|
|
1450
|
+
time.sleep(0.05)
|
|
1451
|
+
# The threads started here count even once they left ``_RELEASES``: a fast failure must still block the delete.
|
|
1452
|
+
with _RELEASES_LOCK:
|
|
1453
|
+
threads = list(dict.fromkeys(started + [thread for thread, owner in _RELEASES.items() if owner is not None and os.path.realpath(owner) == target]))
|
|
1454
|
+
_join(threads, deadline)
|
|
1455
|
+
if any(thread.is_alive() and thread.name.startswith("chat-mcp-") for thread in threads):
|
|
1456
|
+
raise RpcError("The profile is still connecting MCP tools. Retry in a moment.")
|
|
1457
|
+
if holding or any(thread.is_alive() for thread in threads):
|
|
1458
|
+
raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
|
|
1459
|
+
if any(thread.failed for thread in threads):
|
|
1460
|
+
raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
|
|
1461
|
+
release_home(target)
|
|
1462
|
+
yield
|
|
1463
|
+
# The old generation has no worker left that could restore its readiness after deletion.
|
|
1464
|
+
forget_profile_mcp_tools(target)
|
|
1465
|
+
with _MCP_DISCOVERY_LOCK:
|
|
1466
|
+
_MCP_DISCOVERIES.pop(target, None)
|
|
1467
|
+
finally:
|
|
1468
|
+
with _MCP_DISCOVERY_LOCK:
|
|
1469
|
+
_MCP_CLOSING_HOMES.discard(target)
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
def _holding_home(target: str) -> bool:
|
|
1127
1473
|
with _AGENT_CACHE_LOCK:
|
|
1128
|
-
|
|
1129
|
-
started = _release_agents_locked(entries)
|
|
1130
|
-
started += _cancel_background_reviews([(entry[0], entry[2]) for entry in entries], "profile deleted")
|
|
1131
|
-
# The threads started here count even once they left ``_RELEASES``: a fast failure must still block the delete.
|
|
1132
|
-
with _RELEASES_LOCK:
|
|
1133
|
-
threads = list(dict.fromkeys(started + [thread for thread, owner in _RELEASES.items() if owner is not None and os.path.realpath(owner) == target]))
|
|
1134
|
-
_join(threads, time.monotonic() + _PROFILE_RELEASE_TIMEOUT)
|
|
1135
|
-
if any(thread.is_alive() for thread in threads):
|
|
1136
|
-
raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
|
|
1137
|
-
if any(thread.failed for thread in threads):
|
|
1138
|
-
raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
|
|
1474
|
+
return any(os.path.realpath(home) == target for _agent, home in _COMMITTING)
|
|
1139
1475
|
|
|
1140
1476
|
|
|
1141
1477
|
def _acquire_session_db(home):
|
|
@@ -1151,17 +1487,6 @@ def _acquire_session_db(home):
|
|
|
1151
1487
|
return None
|
|
1152
1488
|
|
|
1153
1489
|
|
|
1154
|
-
def _release_session_db(db) -> None:
|
|
1155
|
-
if db is None:
|
|
1156
|
-
return
|
|
1157
|
-
try:
|
|
1158
|
-
from hermes_state_registry import release_or_close
|
|
1159
|
-
|
|
1160
|
-
release_or_close(db)
|
|
1161
|
-
except Exception: # noqa: BLE001
|
|
1162
|
-
log.warning("releasing an agent's state.db handle failed", exc_info=True)
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
1490
|
def _execution_session_id(db, session_id: str) -> str:
|
|
1166
1491
|
"""Predecessor ``_resolve_agent_execution_session_id``: a new agent for a session the Agent rotated by compression
|
|
1167
1492
|
(the old agent went with a sidecar restart or a runtime change) runs on the live tip, with the server's history,
|
|
@@ -1187,6 +1512,20 @@ def _agent_class():
|
|
|
1187
1512
|
return AIAgent
|
|
1188
1513
|
|
|
1189
1514
|
|
|
1515
|
+
def _require_data_use_support(history: list) -> frozenset[str]:
|
|
1516
|
+
"""A downgraded Agent must never send history whose connector privacy requirements it cannot enforce."""
|
|
1517
|
+
if not any(isinstance(row, dict) and row.get("data_use") for row in history):
|
|
1518
|
+
return frozenset()
|
|
1519
|
+
try:
|
|
1520
|
+
from agent.message_metadata import DATA_USE, data_use_requirement
|
|
1521
|
+
except ImportError:
|
|
1522
|
+
pass
|
|
1523
|
+
else:
|
|
1524
|
+
if DATA_USE == "data_use" and callable(data_use_requirement):
|
|
1525
|
+
return data_use_requirement(history)
|
|
1526
|
+
raise RpcError("This Agent release cannot enforce connector privacy requirements. Update Hermes Agent before continuing, compressing or refining this chat.", condition="agent_incompatible")
|
|
1527
|
+
|
|
1528
|
+
|
|
1190
1529
|
def _max_iterations_summary_request() -> str:
|
|
1191
1530
|
"""The user row the Agent appends to ask for a summary once its tool budget runs out; the server drops it."""
|
|
1192
1531
|
try:
|
|
@@ -1326,7 +1665,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1326
1665
|
history = []
|
|
1327
1666
|
if not isinstance(history, list):
|
|
1328
1667
|
raise InvalidParams("conversation_history must be a list")
|
|
1329
|
-
|
|
1668
|
+
_require_data_use_support(history)
|
|
1669
|
+
from hermes_cli.config import load_config_readonly
|
|
1670
|
+
|
|
1671
|
+
# Borrow one snapshot per turn; helpers copy values retained by the Agent.
|
|
1672
|
+
cfg = load_config_readonly() or {}
|
|
1673
|
+
toolsets = _turn_toolsets(params.get("enabled_toolsets"), cfg)
|
|
1330
1674
|
system_message = params.get("system_message")
|
|
1331
1675
|
home = profile_home_param(params)
|
|
1332
1676
|
run = _Run(stream_id, session_id, ctx)
|
|
@@ -1348,8 +1692,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1348
1692
|
raw_emit = ctx.emit
|
|
1349
1693
|
steer_state = {"last": None}
|
|
1350
1694
|
usage_state = {"last": None}
|
|
1695
|
+
pending_mcp = False
|
|
1696
|
+
token_parts: list[str] = []
|
|
1697
|
+
last_token_flush: float | None = None
|
|
1698
|
+
emit_lock = threading.Lock()
|
|
1351
1699
|
|
|
1352
|
-
def
|
|
1700
|
+
def emit_frame(event, data=None):
|
|
1353
1701
|
"""Predecessor ``_webui_steer_events_before``: before content frames, report the Agent's pending steer text
|
|
1354
1702
|
whenever it changed so the server can mark consumed steers live rather than only after ``done``. TAL-397: the
|
|
1355
1703
|
Agent's session counters ride the same boundary, so the server meters cost and tokens while the turn runs."""
|
|
@@ -1363,9 +1711,38 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1363
1711
|
usage_state["last"] = usage
|
|
1364
1712
|
raw_emit("usage", usage)
|
|
1365
1713
|
raw_emit(event, data)
|
|
1714
|
+
|
|
1715
|
+
def flush_tokens():
|
|
1716
|
+
nonlocal last_token_flush
|
|
1717
|
+
if token_parts:
|
|
1718
|
+
text = "".join(token_parts)
|
|
1719
|
+
token_parts.clear()
|
|
1720
|
+
last_token_flush = time.monotonic()
|
|
1721
|
+
emit_frame("token", {"text": text})
|
|
1722
|
+
|
|
1723
|
+
def emit(event, data=None):
|
|
1724
|
+
# Batch before snapshots/serialization; the lock keeps tool-worker and watcher frames in order.
|
|
1725
|
+
with emit_lock:
|
|
1726
|
+
if event == "token":
|
|
1727
|
+
token_parts.append(data["text"])
|
|
1728
|
+
if last_token_flush is None or time.monotonic() - last_token_flush >= 0.016:
|
|
1729
|
+
flush_tokens()
|
|
1730
|
+
else:
|
|
1731
|
+
flush_tokens()
|
|
1732
|
+
emit_frame(event, data)
|
|
1733
|
+
|
|
1734
|
+
timing = getattr(ctx, "timing", None)
|
|
1366
1735
|
try:
|
|
1367
|
-
|
|
1368
|
-
|
|
1736
|
+
if timing:
|
|
1737
|
+
timing.begin("mcp_discovery")
|
|
1738
|
+
mcp_discovery = _discover_mcp_tools(run)
|
|
1739
|
+
pending_mcp = mcp_discovery is not None
|
|
1740
|
+
if timing:
|
|
1741
|
+
timing.end("mcp_discovery", "cancelled" if ctx.cancelled or run.cancel.is_set() else "timeout" if pending_mcp else run.mcp_discovery_outcome)
|
|
1742
|
+
with timing.span("runtime_resolution") if timing else contextlib.nullcontext():
|
|
1743
|
+
runtime = _resolve_runtime(provider, model)
|
|
1744
|
+
if timing:
|
|
1745
|
+
timing.begin("agent_binding")
|
|
1369
1746
|
resolved_model = model or str(runtime.get("model") or "")
|
|
1370
1747
|
resolved_provider = provider or runtime.get("provider")
|
|
1371
1748
|
AIAgent = _agent_class()
|
|
@@ -1449,7 +1826,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1449
1826
|
return metadata
|
|
1450
1827
|
|
|
1451
1828
|
def on_tool(*cb_args, **cb_kwargs):
|
|
1452
|
-
# Structured callbacks carry tool cards; the
|
|
1829
|
+
# Structured callbacks carry tool cards; the Agent already throttles MCP progress.
|
|
1830
|
+
if len(cb_args) >= 3 and cb_args[0] == "tool.progress":
|
|
1831
|
+
emit("tool_progress", {"tool_call_id": str(cb_kwargs.get("tool_call_id") or ""), "message": cb_args[2],
|
|
1832
|
+
"progress": cb_kwargs.get("progress"), "total": cb_kwargs.get("total"),
|
|
1833
|
+
"percent": cb_kwargs.get("percent")})
|
|
1834
|
+
return
|
|
1453
1835
|
if len(cb_args) >= 3 and cb_args[0] in ("reasoning.available", "_thinking"):
|
|
1454
1836
|
text = cb_args[2] if cb_args[0] == "reasoning.available" else cb_args[1]
|
|
1455
1837
|
if text:
|
|
@@ -1634,7 +2016,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1634
2016
|
register_gateway_notify = unregister_gateway_notify = None
|
|
1635
2017
|
approval_cb = _approval_notifier(session_id, emit) if register_gateway_notify is not None else None
|
|
1636
2018
|
|
|
1637
|
-
kwargs = _agent_kwargs(AIAgent, params, session_id, resolved_model, resolved_provider, runtime, toolsets)
|
|
2019
|
+
kwargs = _agent_kwargs(AIAgent, params, session_id, resolved_model, resolved_provider, runtime, toolsets, cfg)
|
|
1638
2020
|
kwargs.update(stream_delta_callback=on_token, reasoning_callback=on_reasoning, tool_progress_callback=on_tool, clarify_callback=clarify_callback)
|
|
1639
2021
|
for name, value in (
|
|
1640
2022
|
("interim_assistant_callback", on_interim),
|
|
@@ -1666,6 +2048,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1666
2048
|
agent = cached[0]
|
|
1667
2049
|
_AGENT_CACHE.move_to_end(session_id)
|
|
1668
2050
|
bind(agent)
|
|
2051
|
+
cached_agent = agent is not None
|
|
1669
2052
|
if agent is not None:
|
|
1670
2053
|
for name in ("stream_delta_callback", "reasoning_callback", "tool_progress_callback", "clarify_callback", "interim_assistant_callback", "tool_start_callback", "tool_complete_callback", "tool_result_metadata_callback", "status_callback", "thinking_callback", "notice_callback", "notice_clear_callback"):
|
|
1671
2054
|
if name in kwargs and hasattr(agent, name):
|
|
@@ -1692,6 +2075,9 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1692
2075
|
_AGENT_CACHE[session_id] = (agent, signature, home)
|
|
1693
2076
|
_release_agents_locked([replaced] if replaced else [])
|
|
1694
2077
|
_evict_idle_agents_locked()
|
|
2078
|
+
if timing:
|
|
2079
|
+
timing.end("agent_binding", "cache_hit" if cached_agent else "ok")
|
|
2080
|
+
timing.begin("conversation_setup")
|
|
1695
2081
|
# Predecessor ``agent.ephemeral_system_prompt``: personality, surface context, progress guidance, and delivery
|
|
1696
2082
|
# hints travel as runtime instructions that are never persisted to history.
|
|
1697
2083
|
ephemeral = params.get("ephemeral_system_prompt")
|
|
@@ -1717,7 +2103,19 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1717
2103
|
log.debug("agent.clear_interrupt failed", exc_info=True)
|
|
1718
2104
|
|
|
1719
2105
|
def _watch_cancel():
|
|
2106
|
+
nonlocal pending_mcp
|
|
1720
2107
|
while not run.cancel.wait(0.25):
|
|
2108
|
+
if pending_mcp and mcp_discovery.is_set():
|
|
2109
|
+
# Discovery can finish while this agent is being built, after the worker's cache refresh.
|
|
2110
|
+
try:
|
|
2111
|
+
with scoped_home(home):
|
|
2112
|
+
refresh_profile_agents_mcp_tools(home)
|
|
2113
|
+
except Exception: # noqa: BLE001 - refresh failure must not disable Stop
|
|
2114
|
+
log.warning("Late MCP refresh failed", exc_info=True)
|
|
2115
|
+
with run.lock:
|
|
2116
|
+
if pending_mcp:
|
|
2117
|
+
emit("notice_clear", {"key": _MCP_PENDING_KEY})
|
|
2118
|
+
pending_mcp = False
|
|
1721
2119
|
if run.finished.is_set():
|
|
1722
2120
|
return
|
|
1723
2121
|
if ctx.cancelled:
|
|
@@ -1765,6 +2163,10 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1765
2163
|
bind_turn_secret_capture = None
|
|
1766
2164
|
log.debug("Agent lacks turn-local skill secret capture; secure setup is unavailable")
|
|
1767
2165
|
with bind_turn_secret_capture(skill_secret_prompt) if bind_turn_secret_capture and params.get("skill_secret") is True else contextlib.nullcontext():
|
|
2166
|
+
if timing:
|
|
2167
|
+
timing.end("conversation_setup")
|
|
2168
|
+
timing.observe("conversation_entry")
|
|
2169
|
+
timing.emit("conversation_entry")
|
|
1768
2170
|
raw = agent.run_conversation(**run_kwargs)
|
|
1769
2171
|
result = raw if isinstance(raw, dict) else {}
|
|
1770
2172
|
except Exception as exc: # noqa: BLE001
|
|
@@ -1827,6 +2229,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1827
2229
|
"live_tool_calls": live_tool_calls,
|
|
1828
2230
|
}
|
|
1829
2231
|
finally:
|
|
2232
|
+
with emit_lock:
|
|
2233
|
+
flush_tokens()
|
|
2234
|
+
with run.lock:
|
|
2235
|
+
if pending_mcp:
|
|
2236
|
+
emit("notice_clear", {"key": _MCP_PENDING_KEY})
|
|
2237
|
+
pending_mcp = False
|
|
1830
2238
|
with _AGENT_CACHE_LOCK:
|
|
1831
2239
|
with _RUNS_LOCK:
|
|
1832
2240
|
_RUNS.pop(stream_id, None)
|
|
@@ -1900,6 +2308,7 @@ def compress(ctx: CallContext, params: dict) -> dict:
|
|
|
1900
2308
|
history = params.get("conversation_history")
|
|
1901
2309
|
if not isinstance(history, list):
|
|
1902
2310
|
raise InvalidParams("conversation_history must be a list")
|
|
2311
|
+
_require_data_use_support(history)
|
|
1903
2312
|
model = str(params.get("model") or "").strip()
|
|
1904
2313
|
provider = str(params.get("model_provider") or "").strip() or None
|
|
1905
2314
|
focus_topic = str(params.get("focus_topic") or "").strip()[:500] or None
|
|
@@ -1987,7 +2396,7 @@ def _refine_agent(params: dict, session_id: str, home):
|
|
|
1987
2396
|
return None
|
|
1988
2397
|
cached = _AGENT_CACHE.get(session_id)
|
|
1989
2398
|
if cached is not None:
|
|
1990
|
-
_COMMITTING.append(cached[0])
|
|
2399
|
+
_COMMITTING.append((cached[0], home))
|
|
1991
2400
|
return cached[0]
|
|
1992
2401
|
model = str(params.get("model") or "").strip()
|
|
1993
2402
|
provider = str(params.get("model_provider") or "").strip() or None
|
|
@@ -2012,7 +2421,7 @@ def _refine_agent(params: dict, session_id: str, home):
|
|
|
2012
2421
|
cached = _AGENT_CACHE.get(session_id)
|
|
2013
2422
|
agent = None if running else cached[0] if cached else built
|
|
2014
2423
|
if agent is not None:
|
|
2015
|
-
_COMMITTING.append(agent)
|
|
2424
|
+
_COMMITTING.append((agent, home))
|
|
2016
2425
|
if agent is built:
|
|
2017
2426
|
built._talaria_unannounced = True # its first turn announces it (``start``)
|
|
2018
2427
|
_AGENT_CACHE[session_id] = (built, signature, home)
|
|
@@ -2030,6 +2439,7 @@ def refine(params: dict) -> str:
|
|
|
2030
2439
|
messages = params.get("messages")
|
|
2031
2440
|
if not session_id or not isinstance(messages, list):
|
|
2032
2441
|
raise InvalidParams("session_id and messages are required")
|
|
2442
|
+
_require_data_use_support(messages)
|
|
2033
2443
|
focus = str(params.get("focus") or "").strip()
|
|
2034
2444
|
with _RUNS_LOCK:
|
|
2035
2445
|
if _session_running_locked(session_id):
|
|
@@ -2049,7 +2459,7 @@ def refine(params: dict) -> str:
|
|
|
2049
2459
|
return f"/refine failed to start: {exc}"
|
|
2050
2460
|
finally:
|
|
2051
2461
|
with _AGENT_CACHE_LOCK:
|
|
2052
|
-
_COMMITTING.remove(agent)
|
|
2462
|
+
_COMMITTING.remove((agent, home))
|
|
2053
2463
|
_release_if_dropped_locked(agent, home)
|
|
2054
2464
|
tail = f" (focus: {focus})" if focus else ""
|
|
2055
2465
|
return f"⚗ Reviewing this conversation in the background{tail} — any memory/skill updates will be reported when done."
|
|
@@ -2074,18 +2484,18 @@ def register(registry) -> None:
|
|
|
2074
2484
|
workspace = params.get("workspace")
|
|
2075
2485
|
if not isinstance(workspace, str) or not workspace.strip():
|
|
2076
2486
|
raise InvalidParams("workspace is required")
|
|
2077
|
-
with
|
|
2487
|
+
with agent_scope(profile_home_param(params)):
|
|
2078
2488
|
with _turn_identity(session_id, workspace):
|
|
2079
2489
|
return start(ctx, params)
|
|
2080
2490
|
|
|
2081
2491
|
@registry.method("chat.compress")
|
|
2082
2492
|
def compress_(ctx: CallContext, params: dict) -> dict:
|
|
2083
|
-
with
|
|
2493
|
+
with agent_scope(profile_home_param(params)):
|
|
2084
2494
|
return compress(ctx, params)
|
|
2085
2495
|
|
|
2086
2496
|
@registry.method("chat.refine")
|
|
2087
2497
|
def refine_(ctx: CallContext, params: dict) -> dict:
|
|
2088
|
-
with
|
|
2498
|
+
with agent_scope(profile_home_param(params)):
|
|
2089
2499
|
return {"output": refine(params)}
|
|
2090
2500
|
|
|
2091
2501
|
@registry.method("chat.compress_finalize", requires_agent=False)
|
|
@@ -2156,15 +2566,22 @@ def register(registry) -> None:
|
|
|
2156
2566
|
def finalize_(ctx: CallContext, params: dict) -> dict:
|
|
2157
2567
|
session_id = str(params.get("session_id") or "").strip()
|
|
2158
2568
|
reason = params.get("reason")
|
|
2159
|
-
if not session_id or reason not in ("clear", "delete"):
|
|
2160
|
-
raise InvalidParams("session_id and reason (clear/delete) are required")
|
|
2569
|
+
if not session_id or reason not in ("clear", "delete", "archive"):
|
|
2570
|
+
raise InvalidParams("session_id and reason (clear/delete/archive) are required")
|
|
2571
|
+
lineage = params.get("lineage")
|
|
2572
|
+
if lineage is not None and (not isinstance(lineage, list) or len(lineage) > 10_000
|
|
2573
|
+
or any(not isinstance(sid, str) or not sid for sid in lineage)):
|
|
2574
|
+
raise InvalidParams("lineage must contain session ids")
|
|
2161
2575
|
home = profile_home_param(params)
|
|
2162
2576
|
messages: list[str] = []
|
|
2163
2577
|
|
|
2164
2578
|
def finalize() -> None:
|
|
2165
2579
|
try:
|
|
2166
2580
|
# Scope inside the worker: a timed-out plugin keeps its own profile until it finishes.
|
|
2167
|
-
with
|
|
2581
|
+
with agent_scope(home):
|
|
2582
|
+
if reason == "archive":
|
|
2583
|
+
_notify_archive(home, session_id, lineage)
|
|
2584
|
+
return
|
|
2168
2585
|
from hermes_cli import lifecycle
|
|
2169
2586
|
|
|
2170
2587
|
callback = getattr(lifecycle, "finalize_session", None)
|
|
@@ -2175,11 +2592,15 @@ def register(registry) -> None:
|
|
|
2175
2592
|
if extract is not None:
|
|
2176
2593
|
messages.extend(extract(results))
|
|
2177
2594
|
except Exception: # noqa: BLE001 - hooks must never block the boundary
|
|
2178
|
-
log.warning("Session
|
|
2595
|
+
log.warning("Session lifecycle callback failed (%s)", reason)
|
|
2179
2596
|
finally:
|
|
2180
2597
|
with _RELEASES_LOCK:
|
|
2181
2598
|
_RELEASES.pop(worker, None)
|
|
2182
2599
|
|
|
2600
|
+
if reason == "archive":
|
|
2601
|
+
worker = _ArchiveCallback(finalize)
|
|
2602
|
+
_schedule_archive(worker, home)
|
|
2603
|
+
return {"messages": []}
|
|
2183
2604
|
worker = threading.Thread(target=finalize, name="session-finalize", daemon=True)
|
|
2184
2605
|
worker.failed = False # type: ignore[attr-defined] - hook failures do not prevent profile deletion
|
|
2185
2606
|
with _RELEASES_LOCK:
|
|
@@ -2187,7 +2608,7 @@ def register(registry) -> None:
|
|
|
2187
2608
|
worker.start()
|
|
2188
2609
|
worker.join(_SESSION_FINALIZE_TIMEOUT)
|
|
2189
2610
|
if worker.is_alive():
|
|
2190
|
-
log.warning("Session
|
|
2611
|
+
log.warning("Session lifecycle callback exceeded deadline (%s)", reason)
|
|
2191
2612
|
return {"messages": []}
|
|
2192
2613
|
return {"messages": messages}
|
|
2193
2614
|
|
|
@@ -2219,12 +2640,52 @@ def register(registry) -> None:
|
|
|
2219
2640
|
_release_vault_session(session_id)
|
|
2220
2641
|
return {"evicted": entry is not None}
|
|
2221
2642
|
|
|
2643
|
+
@registry.method("chat.context_detail", requires_agent=False)
|
|
2644
|
+
def context_detail_(ctx: CallContext, params: dict) -> dict:
|
|
2645
|
+
from ..context_detail import project, unavailable
|
|
2646
|
+
home = profile_home_param(params).resolve()
|
|
2647
|
+
sid = str(params.get("session_id") or "")
|
|
2648
|
+
with _AGENT_CACHE_LOCK:
|
|
2649
|
+
cached = _AGENT_CACHE.get(sid)
|
|
2650
|
+
if cached is None:
|
|
2651
|
+
return unavailable("not_built")
|
|
2652
|
+
agent = cached[0]
|
|
2653
|
+
if (Path(cached[2]).resolve() != home or getattr(agent, "model", None) != params.get("model")
|
|
2654
|
+
or getattr(agent, "provider", None) != params.get("model_provider")):
|
|
2655
|
+
return unavailable("changed")
|
|
2656
|
+
def marks():
|
|
2657
|
+
comp = getattr(agent, "context_compressor", None)
|
|
2658
|
+
return (getattr(agent, "model", None), getattr(agent, "provider", None),
|
|
2659
|
+
getattr(comp, "compression_count", None), getattr(comp, "last_prompt_tokens", None),
|
|
2660
|
+
len(getattr(agent, "_session_messages", None) or []))
|
|
2661
|
+
before = marks()
|
|
2662
|
+
# Pin the cache entry across the read just like refine/memory, so eviction defers its release.
|
|
2663
|
+
_COMMITTING.append((agent, cached[2]))
|
|
2664
|
+
try:
|
|
2665
|
+
from agent.context_breakdown import compute_session_context_breakdown
|
|
2666
|
+
from agent.context_file_sources import context_file_sources_for_agent
|
|
2667
|
+
with scoped_home(home), session_workspace(params.get("workspace")):
|
|
2668
|
+
payload = compute_session_context_breakdown(agent, list(getattr(agent, "_session_messages", None) or []))
|
|
2669
|
+
payload['context_files'] = context_file_sources_for_agent(agent)
|
|
2670
|
+
with _AGENT_CACHE_LOCK:
|
|
2671
|
+
if _AGENT_CACHE.get(sid) is not cached or before != marks():
|
|
2672
|
+
return unavailable("changed")
|
|
2673
|
+
return project(payload)
|
|
2674
|
+
except ImportError:
|
|
2675
|
+
return unavailable("unsupported")
|
|
2676
|
+
except Exception: # never expose Agent diagnostics, prompt contents or paths
|
|
2677
|
+
return unavailable()
|
|
2678
|
+
finally:
|
|
2679
|
+
with _AGENT_CACHE_LOCK:
|
|
2680
|
+
_COMMITTING.remove((agent, cached[2]))
|
|
2681
|
+
_release_if_dropped_locked(agent, cached[2])
|
|
2682
|
+
|
|
2222
2683
|
@registry.method("chat.commit_memory", requires_agent=False)
|
|
2223
2684
|
def commit_memory_(ctx: CallContext, params: dict) -> dict:
|
|
2224
2685
|
"""Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left.
|
|
2225
|
-
Providers get the session's transcript and run under
|
|
2686
|
+
Providers get the session's transcript and run under the ``scoped_home`` the Agent was built in, which v0.22.3
|
|
2687
|
+
binds them to (like ``_release_agent``), not the request's ``profile_home``."""
|
|
2226
2688
|
session_id = str(params.get("session_id") or "").strip()
|
|
2227
|
-
home = profile_home_param(params)
|
|
2228
2689
|
with _AGENT_CACHE_LOCK:
|
|
2229
2690
|
cached = _AGENT_CACHE.get(session_id)
|
|
2230
2691
|
with _RUNS_LOCK:
|
|
@@ -2233,16 +2694,16 @@ def register(registry) -> None:
|
|
|
2233
2694
|
commit = getattr(agent, "commit_memory_session", None) if agent is not None else None
|
|
2234
2695
|
if commit is None or busy:
|
|
2235
2696
|
return {"committed": False}
|
|
2236
|
-
_COMMITTING.append(agent)
|
|
2697
|
+
_COMMITTING.append((agent, cached[2]))
|
|
2237
2698
|
try:
|
|
2238
|
-
with scoped_home(
|
|
2699
|
+
with scoped_home(cached[2]):
|
|
2239
2700
|
commit(list(getattr(agent, "_session_messages", None) or []))
|
|
2240
2701
|
except Exception as exc: # noqa: BLE001
|
|
2241
2702
|
log.warning("commit_memory_session() failed for session %s: %s", session_id, exc)
|
|
2242
2703
|
return {"committed": False}
|
|
2243
2704
|
finally:
|
|
2244
2705
|
with _AGENT_CACHE_LOCK:
|
|
2245
|
-
_COMMITTING.remove(agent)
|
|
2706
|
+
_COMMITTING.remove((agent, cached[2]))
|
|
2246
2707
|
_release_if_dropped_locked(agent, cached[2])
|
|
2247
2708
|
return {"committed": True}
|
|
2248
2709
|
|
|
@@ -2259,7 +2720,7 @@ def register(registry) -> None:
|
|
|
2259
2720
|
raise RpcError(f"approval module unavailable: {exc}", condition="agent_unavailable") from exc
|
|
2260
2721
|
with scoped_home(profile_home_param(params)):
|
|
2261
2722
|
entries = approval_mod.list_gateway_approvals(session_id)
|
|
2262
|
-
targets = [e for e in entries if
|
|
2723
|
+
targets = [e for e in entries if e.get("request_id") == request_id] if request_id else entries[:1]
|
|
2263
2724
|
keys = []
|
|
2264
2725
|
for entry in targets:
|
|
2265
2726
|
keys.extend(entry.get("pattern_keys") or [entry.get("pattern_key", "")])
|
|
@@ -2267,17 +2728,21 @@ def register(registry) -> None:
|
|
|
2267
2728
|
if choice == "session":
|
|
2268
2729
|
for k in keys:
|
|
2269
2730
|
approval_mod.approve_session(session_id, k)
|
|
2270
|
-
elif choice == "always":
|
|
2731
|
+
elif choice == "always" and keys:
|
|
2732
|
+
permanent_set = getattr(approval_mod, "_permanent_set", None)
|
|
2733
|
+
baseline_key = getattr(approval_mod, "_baseline_key", None)
|
|
2734
|
+
baselines = getattr(approval_mod, "_permanent_baseline_by_home", None)
|
|
2735
|
+
if not callable(permanent_set) or not callable(baseline_key) or not isinstance(baselines, dict):
|
|
2736
|
+
raise RpcError("Agent cannot safely persist command approvals", condition="agent_incompatible")
|
|
2271
2737
|
for k in keys:
|
|
2272
2738
|
approval_mod.approve_session(session_id, k)
|
|
2273
2739
|
approval_mod.approve_permanent(k)
|
|
2274
2740
|
try:
|
|
2275
|
-
#
|
|
2276
|
-
#
|
|
2277
|
-
permanent_set = getattr(approval_mod, "_permanent_set", None)
|
|
2741
|
+
# Older retained Agents' _persist_choice saves full snapshots. Capture only unsaved grants
|
|
2742
|
+
# against this home's baseline, so a concurrent revocation cannot become fresh approval intent.
|
|
2278
2743
|
with approval_mod._lock:
|
|
2279
|
-
|
|
2280
|
-
approval_mod.save_permanent_allowlist(
|
|
2744
|
+
grants = permanent_set() - baselines.get(baseline_key(), set())
|
|
2745
|
+
approval_mod.save_permanent_allowlist(grants)
|
|
2281
2746
|
except Exception: # noqa: BLE001
|
|
2282
2747
|
log.debug("save_permanent_allowlist failed", exc_info=True)
|
|
2283
2748
|
resolved = approval_mod.resolve_gateway_approval(session_id, choice, request_id=request_id)
|