@maudecode/talaria-web 1.21.0 → 1.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release.json +4 -4
- package/dist/api/automation-router.d.ts +14 -14
- package/dist/api/chat-router.d.ts +225 -68
- package/dist/api/groups-router.d.ts +764 -0
- package/dist/api/groups-router.js +79 -0
- package/dist/api/groups-router.js.map +1 -0
- package/dist/api/raw-routes.d.ts +1 -1
- package/dist/api/raw-routes.js +15 -3
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +41 -0
- package/dist/api/router.js +2 -0
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +590 -160
- package/dist/api/sessions-router.js +14 -14
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +948 -1
- package/dist/api/settings-router.js +54 -2
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +1 -1
- package/dist/api/sse-routes.js +11 -3
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +27 -136
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +109 -24
- package/dist/api/tools-router.js +69 -9
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +5 -1
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +5 -1
- package/dist/auth/gate.js.map +1 -1
- package/dist/cli/ctl.d.ts +5 -2
- package/dist/cli/ctl.js +90 -42
- package/dist/cli/ctl.js.map +1 -1
- package/dist/config/agent-config.d.ts +6 -1
- package/dist/config/agent-config.js +75 -3
- package/dist/config/agent-config.js.map +1 -1
- package/dist/feedback.d.ts +21 -0
- package/dist/feedback.js +328 -0
- package/dist/feedback.js.map +1 -0
- package/dist/fs/stat.d.ts +2 -0
- package/dist/fs/stat.js +4 -0
- package/dist/fs/stat.js.map +1 -1
- package/dist/http/context.d.ts +10 -1
- package/dist/http/context.js +0 -6
- package/dist/http/context.js.map +1 -1
- package/dist/http/pinned.js +19 -8
- package/dist/http/pinned.js.map +1 -1
- package/dist/providers/catalog.d.ts +10 -2
- package/dist/providers/catalog.js +113 -15
- package/dist/providers/catalog.js.map +1 -1
- package/dist/redact.js +9 -3
- package/dist/redact.js.map +1 -1
- package/dist/release.d.ts +14 -1
- package/dist/release.js +40 -2
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +29 -5
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +3 -2
- package/dist/sessions/anchor.js +11 -10
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/cli-sessions.js +11 -6
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/compress.d.ts +10 -9
- package/dist/sessions/compress.js +31 -20
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +11 -0
- package/dist/sessions/gateway-sessions.js +79 -24
- package/dist/sessions/gateway-sessions.js.map +1 -1
- package/dist/sessions/gateway-watcher.js +8 -4
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/groups.d.ts +60 -0
- package/dist/sessions/groups.js +339 -0
- package/dist/sessions/groups.js.map +1 -0
- package/dist/sessions/list.d.ts +8 -3
- package/dist/sessions/list.js +17 -6
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +14 -5
- package/dist/sessions/merge.js +42 -13
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +1 -1
- package/dist/sessions/pending.js +15 -6
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +1 -14
- package/dist/sessions/prefix-index.js +1 -50
- package/dist/sessions/prefix-index.js.map +1 -1
- package/dist/sessions/reasoning-display.d.ts +2 -0
- package/dist/sessions/reasoning-display.js +31 -0
- package/dist/sessions/reasoning-display.js.map +1 -0
- package/dist/sessions/relay.d.ts +34 -2
- package/dist/sessions/relay.js +84 -7
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +7 -2
- package/dist/sessions/service.js +106 -64
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +6 -0
- package/dist/sessions/session.js +13 -0
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.d.ts +3 -1
- package/dist/sessions/shares.js +33 -11
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.js +2 -0
- package/dist/sessions/source-kind.js.map +1 -1
- package/dist/sessions/state-db.d.ts +2 -1
- package/dist/sessions/state-db.js +12 -5
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/storage/json-documents.js +2 -0
- package/dist/sessions/storage/json-documents.js.map +1 -1
- package/dist/sessions/store.d.ts +1 -0
- package/dist/sessions/store.js +10 -3
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +7 -2
- package/dist/sessions/streams.js +16 -6
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +2 -0
- package/dist/sessions/titles.js +11 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/todo.js +10 -1
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/turn.js +50 -21
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.js +6 -1
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +3 -0
- package/dist/sidecar/client.js +2 -1
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/discover.js +3 -2
- package/dist/sidecar/discover.js.map +1 -1
- package/dist/spa.js +1 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/group-fixture.d.ts +66 -0
- package/dist/test/group-fixture.js +89 -0
- package/dist/test/group-fixture.js.map +1 -0
- package/dist/test/harness.d.ts +3 -0
- package/dist/test/harness.js +3 -0
- package/dist/test/harness.js.map +1 -1
- package/dist/tools/connectors.d.ts +9 -1
- package/dist/tools/connectors.js +48 -0
- package/dist/tools/connectors.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +1 -1
- package/dist/tools/insights.d.ts +3 -0
- package/dist/tools/insights.js +6 -7
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/skills.d.ts +6 -0
- package/dist/tools/skills.js +17 -0
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +2 -0
- package/dist/tools/update-notifications.js +7 -3
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +26 -9
- package/dist/tools/updates.js +144 -47
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +2 -0
- package/dist/workspace/fs.js +63 -1
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/media-refs.js +10 -3
- package/dist/workspace/media-refs.js.map +1 -1
- package/dist/workspace/upload.d.ts +1 -1
- package/dist/workspace/upload.js +52 -37
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +5 -1
- package/dist/workspace/workspaces.js +32 -6
- package/dist/workspace/workspaces.js.map +1 -1
- package/package.json +4 -3
- package/sidecar/agent_importer.json +8 -0
- package/sidecar/scripts/agent_importer_contract.py +19 -0
- package/sidecar/scripts/suite_runner.py +55 -8
- package/sidecar/scripts/test.sh +3 -1
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +12 -5
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/agent_imports.py +346 -0
- package/sidecar/talaria_sidecar/methods/chat.py +141 -58
- package/sidecar/talaria_sidecar/methods/commands.py +6 -4
- package/sidecar/talaria_sidecar/methods/config.py +8 -3
- package/sidecar/talaria_sidecar/methods/connectors.py +138 -4
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +21 -0
- package/sidecar/talaria_sidecar/methods/delegation.py +126 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +5 -0
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +35 -6
- package/sidecar/talaria_sidecar/methods/groups.py +120 -0
- package/sidecar/talaria_sidecar/methods/memory.py +27 -3
- package/sidecar/talaria_sidecar/methods/nous_account.py +130 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +25 -4
- package/sidecar/talaria_sidecar/methods/outbound_drafts.py +1 -19
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +73 -2
- package/sidecar/talaria_sidecar/methods/providers.py +51 -1
- package/sidecar/talaria_sidecar/methods/skill_revisions.py +177 -0
- package/sidecar/talaria_sidecar/methods/skills.py +10 -0
- package/sidecar/talaria_sidecar/methods/tts.py +67 -0
- package/sidecar/talaria_sidecar/rpc.py +11 -2
- package/static/dist/FILES.txt +154 -143
- package/static/dist/THIRD_PARTY_LICENSES.md +1 -1
- package/static/dist/assets/AppShell-jwd95ofg.js +8 -0
- package/static/dist/assets/{Button-nuaikybd.js → Button-nrcj7shj.js} +1 -1
- package/static/dist/assets/ChatPage-fmgk8xpk.js +2 -0
- package/static/dist/assets/Composer-oya4ggu7.js +5 -0
- package/static/dist/assets/ConnectTargets-rg90uiui.js +1 -0
- package/static/dist/assets/Dialog-gk0uatl8.js +1 -0
- package/static/dist/assets/ExtensionRoute-ji4xce9l.js +1 -0
- package/static/dist/assets/HubRoute-2pgy4aa2.js +2 -0
- package/static/dist/assets/Markdown-d6br42ts.js +2 -0
- package/static/dist/assets/{MarkdownRenderer-ccgqrza3.js → MarkdownRenderer-k8rmb83g.js} +6 -6
- package/static/dist/assets/OAuthSignIn-pbls9ljk.js +1 -0
- package/static/dist/assets/QueueCard-liib58ru.js +1 -0
- package/static/dist/assets/ResourceSwitch-gtdv2jgs.js +1 -0
- package/static/dist/assets/RightPanel-b89e4kx3.js +1 -0
- package/static/dist/assets/Select-l6qybenp.js +1 -0
- package/static/dist/assets/SessionListPanel-l4vy95j8.js +1 -0
- package/static/dist/assets/SettingsLayout-chw7pl2m.js +1 -0
- package/static/dist/assets/TerminalPanel-qrd0gkx7.js +16 -0
- package/static/dist/assets/Toaster-ihzgaq9z.js +1 -0
- package/static/dist/assets/_app-cn1au4zq.js +1 -0
- package/static/dist/assets/{_app.ext._extensionId-n797jp72.js → _app.ext._extensionId-m4npkrr0.js} +1 -1
- package/static/dist/assets/_app.groups-c8ycbp5n.js +1 -0
- package/static/dist/assets/{_app.index-ne43mdw2.js → _app.index-f1fckz3h.js} +1 -1
- package/static/dist/assets/{_app.insights-fqonovb4.js → _app.insights-ezivk7ad.js} +1 -1
- package/static/dist/assets/{_app.kanban-kysliv19.js → _app.kanban-zbkeov0f.js} +1 -1
- package/static/dist/assets/{_app.logs-febut82r.js → _app.logs-dqam5zyz.js} +1 -1
- package/static/dist/assets/{_app.memory-ha86oj1c.js → _app.memory-gzxkm3a0.js} +1 -1
- package/static/dist/assets/{_app.profiles-cnu3gxxq.js → _app.profiles-j28l31hl.js} +1 -1
- package/static/dist/assets/{_app.session._sessionId-ifzjpcgn.js → _app.session._sessionId-55egsufs.js} +1 -1
- package/static/dist/assets/{_app.settings-nmakdiaf.js → _app.settings-f5ltpieb.js} +1 -1
- package/static/dist/assets/_app.settings._section-dda0vfot.js +3 -0
- package/static/dist/assets/{_app.skills-f3wad8yo.js → _app.skills-kuc81hq0.js} +1 -1
- package/static/dist/assets/_app.tasks-gyy6vj5l.js +1 -0
- package/static/dist/assets/{_app.todos-bml1m3r1.js → _app.todos-d0xoy68t.js} +1 -1
- package/static/dist/assets/{_app.workspaces-f3d066lx.js → _app.workspaces-pawp2gxk.js} +1 -1
- package/static/dist/assets/_delete-qnxuo87s.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-i53jc4yp.js → abnfDiagram-VCTEODGH-mhav7n5a.js} +1 -1
- package/static/dist/assets/{architecture-7GRP2DOG-e3bv5m6f.js → architecture-7GRP2DOG-ozbanukt.js} +1 -1
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-pakekxu2.js → architectureDiagram-5GKGNRK7-febtor5m.js} +1 -1
- package/static/dist/assets/archive-gyk9c34a.js +1 -0
- package/static/dist/assets/autoReadClaim-nlyv8tu9.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bc6bo1n6.js → blockDiagram-I7D4REHJ-ocais2jf.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-btvwk2qx.js → c4Diagram-7LVT6UL2-cgpfjx5f.js} +1 -1
- package/static/dist/assets/channel-els5ri1f.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-dlwab11c.js → chunk-4HAMMTFA-hvqdiiow.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-dp5gh7i8.js → chunk-75Z2AOVW-hxbulvtb.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bi06zl1c.js → chunk-DU6HZSFF-kkp6a7gp.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-kvv1rnlh.js → chunk-F27PBJKO-ljz0gssc.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ji0rx9by.js → chunk-GMAD6QVW-bo0enyxn.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-nmz0j82p.js → chunk-GVQU2GXP-cnq7l2v6.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-kmi1qx88.js → chunk-IMKFNOWR-gjnkrfu6.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-xhb7mebj.js → chunk-L3NEJ4N5-gjfp71qi.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-5a9sgscq.js → chunk-OSK3NFVY-ieq5r94b.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-dfezp4vu.js → chunk-P2QGCYS3-hdefjk56.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-gd2vq3c0.js → chunk-POPQ4Y6H-mcbt8wpr.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-wvw2nksf.js → chunk-PWAF6VOD-mx2drja9.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-f58q97px.js → chunk-SHT3W25Y-j7dd9i66.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-abgwpyz9.js → chunk-SVP7TREG-jcnt3lxt.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-h4sbs8ds.js → chunk-TICWLB2K-lrwv5hx9.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-krpt9pdd.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-krpt9pdd.js +1 -0
- package/static/dist/assets/completionAudio-fhxf0h9j.js +1 -0
- package/static/dist/assets/{copy-m9h56qa8.js → copy-gyzrqmyr.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-g081vffs.js → cynefin-OW5HDTMX-heq36srt.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-odxvjesk.js → cynefinDiagram-5FMLGOSQ-crrmq338.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cpdwzjs4.js → dagre-GXQ25YYZ-m7fpnt6k.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-clv5mxvu.js → diagram-S7CK7UJ4-ebm1ebv6.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-m2ia1mbf.js → diagram-UQ7AKVKN-hc0ypsp1.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-ea6zv636.js → diagram-VSXAHHWV-mu8q5n4b.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-oqlef685.js → diagram-VX7I27RA-i488wdlb.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-kjubcbwu.js → diagram-Z3DM3KII-gz3nwvq9.js} +1 -1
- package/static/dist/assets/{dist-carq1azj.js → dist-ge7hom9n.js} +4 -4
- package/static/dist/assets/{dist-crc46di9.js → dist-kpg5ccm9.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-fg0zri3e.js → ebnfDiagram-PWID7BFC-lwskecx4.js} +1 -1
- package/static/dist/assets/{erDiagram-RLTQ6QDP-eb7pmk3x.js → erDiagram-RLTQ6QDP-c0bysigr.js} +1 -1
- package/static/dist/assets/{eventmodeling-NTZA5JFV-gvi1fxhn.js → eventmodeling-NTZA5JFV-k19e509j.js} +1 -1
- package/static/dist/assets/feedbackVideo.worker-JBVT3sDD.js +480 -0
- package/static/dist/assets/flowDiagram-HODETNUW-n4m8e6kq.js +1 -0
- package/static/dist/assets/folder-plus-m1ywe0cv.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-jmisowrc.js → ganttDiagram-EL5Y4UJY-mz1p8yzt.js} +1 -1
- package/static/dist/assets/getPseudoElementBounds-ci0x9gki.js +1 -0
- package/static/dist/assets/{gitGraph-4MIJSDKK-m7kt3u6e.js → gitGraph-4MIJSDKK-h0uskiog.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-nq7vfzh2.js → gitGraphDiagram-WWUBYQGX-e4d9um1j.js} +1 -1
- package/static/dist/assets/group_discussions-fk5hry4l.js +1 -0
- package/static/dist/assets/{highlighted-body-KPVGNVTW-gcu017ay.js → highlighted-body-KPVGNVTW-d47n6oqu.js} +1 -1
- package/static/dist/assets/{host-ipp4e84c.js → host-f7eudvft.js} +1 -1
- package/static/dist/assets/index-lab8g9gs.css +2 -0
- package/static/dist/assets/index-n5rg2q2r.js +13 -0
- package/static/dist/assets/{info-A6RAGUB7-idazwfqe.js → info-A6RAGUB7-k04of9c6.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-bb9jh8gl.js → infoDiagram-27XIBGKW-l98n1tgz.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-nqc132rv.js → ishikawaDiagram-5VMMS53U-gkznu2d6.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-fpvnhy44.js → journeyDiagram-3NMN7TZE-mt23qvx1.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-ndzqm6fu.js → kanban-definition-UXKFOSKX-ab93l7y5.js} +1 -1
- package/static/dist/assets/katex-ic33v820.js +257 -0
- package/static/dist/assets/katex-qcv7xo0t.css +1 -0
- package/static/dist/assets/{lazyRouteComponent-javud235.js → lazyRouteComponent-dt9efiqk.js} +1 -1
- package/static/dist/assets/{line-g625e7cj.js → line-dkm9r3as.js} +1 -1
- package/static/dist/assets/link-2-ozyjqwhh.js +1 -0
- package/static/dist/assets/{link-gfdaho08.js → link-dip3g6zs.js} +1 -1
- package/static/dist/assets/{locale-cs-n41hptse.js → locale-cs-brltdkcp.js} +1 -1
- package/static/dist/assets/{locale-de-k7ht47ew.js → locale-de-h6e9oq6v.js} +1 -1
- package/static/dist/assets/{locale-es-ih078lco.js → locale-es-oklmh80f.js} +1 -1
- package/static/dist/assets/{locale-fr-ojabnf41.js → locale-fr-b17ozoi2.js} +1 -1
- package/static/dist/assets/{locale-it-kb96jufy.js → locale-it-whe01eef.js} +1 -1
- package/static/dist/assets/{locale-ja-jpsp3xs1.js → locale-ja-g1s0rtpc.js} +1 -1
- package/static/dist/assets/{locale-pl-dbap4leu.js → locale-pl-d9p3lqcb.js} +1 -1
- package/static/dist/assets/{locale-pt-nci7w72x.js → locale-pt-l75on74f.js} +1 -1
- package/static/dist/assets/{locale-ru-soghe4cs.js → locale-ru-wl3mbsaw.js} +1 -1
- package/static/dist/assets/{locale-tr-ks00mu68.js → locale-tr-cz87itrq.js} +1 -1
- package/static/dist/assets/{locale-vi-ij7twfm6.js → locale-vi-elqj1y4k.js} +1 -1
- package/static/dist/assets/{locale-zh-Hant-feakwr7a.js → locale-zh-Hant-hf8gqm7d.js} +1 -1
- package/static/dist/assets/{locale-zh-ren0vjyv.js → locale-zh-cdckwb0u.js} +1 -1
- package/static/dist/assets/login-go5xfzkx.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-o7wh7c74.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-plzkuzov.js → mermaid-parser.core-c4ywstu7.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-ojrgs569.js → mindmap-definition-YA3MSWOX-mnvkgz5u.js} +1 -1
- package/static/dist/assets/onboarding-hbbpvp6w.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-gyaf9s8h.js → packet-AYTQ26CC-dfm2ygox.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bf6xj8mq.js → pegDiagram-XKGWAZYB-kfbyls4v.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-f5ymzelo.js → pie-WAS4IAKB-jniw7r2b.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-d0qjl3zi.js → pieDiagram-E7YTZNPT-cir9nlg4.js} +1 -1
- package/static/dist/assets/presence-ivo54s19.js +1 -0
- package/static/dist/assets/prompt_queue_cancelled-cebcuagu.js +1 -0
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-oqy1mras.js → quadrantDiagram-AXDQQJYC-2nnb2u4m.js} +1 -1
- package/static/dist/assets/queryKeys-mbi6o5vh.js +9 -0
- package/static/dist/assets/{radar-RG4KPBEZ-b6jcjvda.js → radar-RG4KPBEZ-jfvtqhq2.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-e3a6lk5k.js → railroad-74A4TZTK-hnm0ikx7.js} +1 -1
- package/static/dist/assets/{railroad-abnf-HS5TGJTU-ff5hocrx.js → railroad-abnf-HS5TGJTU-hcfz5gyc.js} +1 -1
- package/static/dist/assets/{railroad-ebnf-LZEXJU2U-exkhyw4n.js → railroad-ebnf-LZEXJU2U-ocbvmcwo.js} +1 -1
- package/static/dist/assets/{railroad-peg-WCYAUIDC-jefk9b7c.js → railroad-peg-WCYAUIDC-kkbzgo8d.js} +1 -1
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-e4amks1i.js → railroadDiagram-O6MQD6OU-ox5v7853.js} +1 -1
- package/static/dist/assets/rename-ccd52yws.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-c77k1e2y.js → requirementDiagram-BXWQKSXE-e1iq54h4.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-oa1cc52s.js → sankeyDiagram-P5KCCOFB-mmza9r2u.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-j20gqt22.js → sequenceDiagram-WJ2MYXX4-dioic69h.js} +1 -1
- package/static/dist/assets/{share._token-kmfoflw6.js → share._token-i51g4ccy.js} +1 -1
- package/static/dist/assets/sliders-horizontal-nj1dipq4.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-co2ghkdp.js → stateDiagram-D77RDMKH-do5wvqjw.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-f0sedx52.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-m30cktqe.js → swimlanes-42K2YHIH-mhjzgtgl.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n0x444kc.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-liuep6fl.js → timeline-definition-24CTP7MA-i5820ppg.js} +1 -1
- package/static/dist/assets/toast-esv4ma70.js +56 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-np5d2jv2.js → treeView-Q6P3EWNA-btp8feob.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-cmpltxh1.js → treemap-WGGIJYW6-lv71dt7p.js} +1 -1
- package/static/dist/assets/tts-bq7ax6xp.js +1 -0
- package/static/dist/assets/{useForm-f1udzsjf.js → useForm-d5wxx0l0.js} +1 -1
- package/static/dist/assets/{useParams-ierevvts.js → useParams-gk1u0y6m.js} +1 -1
- package/static/dist/assets/{useSearch-i7nr9ypl.js → useSearch-nsrme1pf.js} +1 -1
- package/static/dist/assets/useTranscript-m9ve9mml.js +1 -0
- package/static/dist/assets/{vennDiagram-4TSXK5OY-b2g42qrg.js → vennDiagram-4TSXK5OY-bffv7e11.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lb0eqbff.js → wardley-WFR3VGLG-f1m16ivx.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ermwaqha.js → wardleyDiagram-VM6X3IG4-ous4pq48.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dll0jldk.js → xychartDiagram-S5SC5T6Z-jxgbp4as.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-c5989995.js +0 -8
- package/static/dist/assets/ChatPage-bfg4khph.js +0 -6
- package/static/dist/assets/ConnectTargets-k5wo9yo8.js +0 -1
- package/static/dist/assets/Dialog-o8vo2aws.js +0 -1
- package/static/dist/assets/ExtensionRoute-l1yjrso4.js +0 -1
- package/static/dist/assets/Field-j7o8cfps.js +0 -1
- package/static/dist/assets/HubRoute-efnwe4xz.js +0 -2
- package/static/dist/assets/Markdown-clqf5m22.js +0 -2
- package/static/dist/assets/OAuthSignIn-h7bjaqp6.js +0 -1
- package/static/dist/assets/ResourceSwitch-oai2zqaj.js +0 -1
- package/static/dist/assets/RightPanel-ho161a45.js +0 -1
- package/static/dist/assets/Select-d1g5a8bm.js +0 -1
- package/static/dist/assets/SessionListPanel-i7kaokhi.js +0 -1
- package/static/dist/assets/SettingsLayout-lnecm87w.js +0 -1
- package/static/dist/assets/States-easntmry.js +0 -1
- package/static/dist/assets/TerminalPanel-lxhwd4jl.js +0 -16
- package/static/dist/assets/Toaster-l4sciy8t.js +0 -1
- package/static/dist/assets/_app-mqexqbg8.js +0 -1
- package/static/dist/assets/_app.settings._section-djglnjmi.js +0 -3
- package/static/dist/assets/_app.tasks-l10py1c0.js +0 -1
- package/static/dist/assets/archive-ihqi7z2x.js +0 -1
- package/static/dist/assets/channel-jn4f8443.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-dzrw66se.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-dzrw66se.js +0 -1
- package/static/dist/assets/completionAudio-ce86msbu.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-abekab15.js +0 -1
- package/static/dist/assets/folder-plus-ksaons88.js +0 -1
- package/static/dist/assets/index-d6vbxlra.js +0 -13
- package/static/dist/assets/index-ey6qo0zx.css +0 -2
- package/static/dist/assets/katex-cbdd1ohw.css +0 -1
- package/static/dist/assets/katex-pbij2oo6.js +0 -257
- package/static/dist/assets/login-iv6yq4xf.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-cwj5cl6k.js +0 -1
- package/static/dist/assets/onboarding-ff4emacu.js +0 -1
- package/static/dist/assets/queryKeys-x8fjxhut.js +0 -9
- package/static/dist/assets/refresh-cw-lfp4u1oh.js +0 -1
- package/static/dist/assets/sliders-horizontal-g5jkzd9k.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-faw1opgh.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hfil433g.js +0 -8
- package/static/dist/assets/toast-zmb35u3r.js +0 -56
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Profile import adapter. Native planning runs on private snapshots; publication uses anchored descriptors."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from contextlib import contextmanager, nullcontext
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from itertools import islice
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import secrets
|
|
12
|
+
import stat
|
|
13
|
+
import tempfile
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
from .. import home as homes
|
|
17
|
+
from ..errors import InvalidParams, RpcError
|
|
18
|
+
from . import config, memory, plugin_catalog
|
|
19
|
+
|
|
20
|
+
_SOURCES = {'claude-code': ('.claude', 'Claude Code'), 'codex': ('.codex', 'Codex CLI'), 'cursor': ('.cursor', 'Cursor')}
|
|
21
|
+
_CREDENTIALS = {'.credentials.json', 'auth.json', 'credentials.json', 'cli-config.json', '.env'}
|
|
22
|
+
_TARGETS = ('config.yaml', 'memories', 'skills', 'import-sync.json')
|
|
23
|
+
_MEMORY_FILES = {'memories/MEMORY.md', 'memories/USER.md'}
|
|
24
|
+
_PREVIEWS = {}
|
|
25
|
+
_MAX_FILES = 2048
|
|
26
|
+
_MAX_BYTES = 16 * 1024 * 1024
|
|
27
|
+
_LEDGER = '.talaria-agent-imports'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _result(status, *, items=None, stripped=None, receipt=None, sources=None, message=None, can_apply=False, catalog_required=False):
|
|
31
|
+
return dict(status=status, items=items or [], stripped_secrets=stripped or [], receipt=receipt,
|
|
32
|
+
sources=sources or [], message=message, can_apply=can_apply, catalog_required=catalog_required)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@contextmanager
|
|
36
|
+
def _directory(path):
|
|
37
|
+
"""Open every ancestor without following links; the held descriptor owns all subsequent IO."""
|
|
38
|
+
path = Path(path)
|
|
39
|
+
if not path.is_absolute():
|
|
40
|
+
raise OSError('An absolute directory is required')
|
|
41
|
+
fd = os.open(path.anchor, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
42
|
+
try:
|
|
43
|
+
for part in path.parts[1:]:
|
|
44
|
+
child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
|
|
45
|
+
os.close(fd)
|
|
46
|
+
fd = child
|
|
47
|
+
yield fd
|
|
48
|
+
finally:
|
|
49
|
+
os.close(fd)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _read(fd, name):
|
|
53
|
+
child = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=fd)
|
|
54
|
+
try:
|
|
55
|
+
info = os.fstat(child)
|
|
56
|
+
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size > _MAX_BYTES:
|
|
57
|
+
raise OSError('Unsafe or oversized import file')
|
|
58
|
+
with os.fdopen(os.dup(child), 'rb') as stream:
|
|
59
|
+
body = stream.read(_MAX_BYTES + 1)
|
|
60
|
+
after = os.fstat(child)
|
|
61
|
+
if (info.st_size, info.st_mtime_ns, info.st_ctime_ns) != (after.st_size, after.st_mtime_ns, after.st_ctime_ns):
|
|
62
|
+
raise OSError('Import input changed during read')
|
|
63
|
+
return body, (info.st_dev, info.st_ino, info.st_mode, info.st_mtime_ns, info.st_ctime_ns)
|
|
64
|
+
finally:
|
|
65
|
+
os.close(child)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _snapshot(path, names):
|
|
69
|
+
"""Bounded snapshot of only importer inputs. Credential stores are never read."""
|
|
70
|
+
files, identities = {}, {}
|
|
71
|
+
def walk(fd, name, rel):
|
|
72
|
+
try:
|
|
73
|
+
info = os.stat(name, dir_fd=fd, follow_symlinks=False)
|
|
74
|
+
except FileNotFoundError:
|
|
75
|
+
return
|
|
76
|
+
if rel.endswith('.lock') and rel[:-5] in _MEMORY_FILES:
|
|
77
|
+
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
|
|
78
|
+
raise OSError('Unsafe memory lock')
|
|
79
|
+
return
|
|
80
|
+
if stat.S_ISDIR(info.st_mode):
|
|
81
|
+
child = os.open(name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
|
|
82
|
+
try:
|
|
83
|
+
identities[rel] = (info.st_dev, info.st_ino, info.st_mode)
|
|
84
|
+
if len(identities) > _MAX_FILES:
|
|
85
|
+
raise OSError('Import tree too large')
|
|
86
|
+
with os.scandir(child) as entries:
|
|
87
|
+
names = [entry.name for entry in islice(entries, _MAX_FILES + 1)]
|
|
88
|
+
if len(names) > _MAX_FILES:
|
|
89
|
+
raise OSError('Import directory too large')
|
|
90
|
+
for entry in sorted(names):
|
|
91
|
+
if entry not in _CREDENTIALS and entry not in {'.git', '__pycache__'}:
|
|
92
|
+
walk(child, entry, rel + '/' + entry)
|
|
93
|
+
finally:
|
|
94
|
+
os.close(child)
|
|
95
|
+
else:
|
|
96
|
+
body, identity = _read(fd, name)
|
|
97
|
+
files[rel], identities[rel] = body, identity
|
|
98
|
+
if len(identities) > _MAX_FILES or sum(map(len, files.values())) > _MAX_BYTES:
|
|
99
|
+
raise OSError('Import tree too large')
|
|
100
|
+
with _directory(path) as fd:
|
|
101
|
+
info = os.fstat(fd)
|
|
102
|
+
identities['.'] = (info.st_dev, info.st_ino, info.st_mode)
|
|
103
|
+
for name in names:
|
|
104
|
+
walk(fd, name, name)
|
|
105
|
+
return files, identities
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _source(agent):
|
|
109
|
+
os_home = Path.home()
|
|
110
|
+
source = os_home / _SOURCES[agent][0]
|
|
111
|
+
names = {'claude-code': ['CLAUDE.md', 'settings.json', 'skills', 'commands'],
|
|
112
|
+
'codex': ['AGENTS.md', 'config.toml', 'memories', 'skills'],
|
|
113
|
+
'cursor': ['AGENTS.md', 'mcp.json', 'rules', 'skills']}[agent]
|
|
114
|
+
files, identity = _snapshot(source, names)
|
|
115
|
+
if agent == 'claude-code':
|
|
116
|
+
extra, extra_identity = _snapshot(os_home, ['.claude.json'])
|
|
117
|
+
files.update({'../' + k: v for k, v in extra.items()})
|
|
118
|
+
identity.update({'../' + k: v for k, v in extra_identity.items()})
|
|
119
|
+
return source, files, identity
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _stamp(snapshot):
|
|
123
|
+
files, identities = snapshot
|
|
124
|
+
return hashlib.sha256(json.dumps([identities, {k: hashlib.sha256(v).hexdigest() for k, v in files.items()}], sort_keys=True).encode()).hexdigest()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _materialize(root, files, identities):
|
|
128
|
+
root.mkdir()
|
|
129
|
+
for name, info in identities.items():
|
|
130
|
+
if name != '.' and stat.S_ISDIR(info[2]):
|
|
131
|
+
(root / name).mkdir(parents=True, exist_ok=True)
|
|
132
|
+
for name, body in files.items():
|
|
133
|
+
path = root / name
|
|
134
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
path.write_bytes(body)
|
|
136
|
+
path.chmod(stat.S_IMODE(identities[name][2]))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _project(report):
|
|
140
|
+
# No native reason, path, command, content or arbitrary detail crosses the boundary.
|
|
141
|
+
kinds = {'source': 'Source', 'settings': 'Settings', 'claude-md': 'Claude Code instructions',
|
|
142
|
+
'agents-md': 'Instructions', 'command-allowlist': 'Allowed commands', 'command-denylist': 'Denied commands',
|
|
143
|
+
'mcp-servers': 'MCP servers', 'skills': 'Skills', 'skill': 'Skill', 'memories': 'Memories',
|
|
144
|
+
'rules': 'Cursor rules', 'config': 'Configuration', 'slash-commands': 'Slash commands'}
|
|
145
|
+
statuses = {'imported', 'skipped', 'conflict', 'error'}
|
|
146
|
+
items = report.get('items')
|
|
147
|
+
if not isinstance(items, list) or len(items) > _MAX_FILES:
|
|
148
|
+
raise ValueError('Invalid native report')
|
|
149
|
+
projected = []
|
|
150
|
+
for item in items:
|
|
151
|
+
if not isinstance(item, dict) or item.get('kind') not in kinds or item.get('status') not in statuses:
|
|
152
|
+
raise ValueError('Invalid native disposition')
|
|
153
|
+
projected.append(dict(kind=item['kind'], status=item['status'], label=kinds[item['kind']]))
|
|
154
|
+
stripped = report.get('stripped_secrets', [])
|
|
155
|
+
if not isinstance(stripped, list) or len(stripped) > _MAX_FILES:
|
|
156
|
+
raise ValueError('Invalid stripped-secret names')
|
|
157
|
+
return projected, [name for name in stripped if isinstance(name, str) and len(name) <= 200
|
|
158
|
+
and re.fullmatch(r'[A-Za-z0-9_.-]+', name)]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@contextmanager
|
|
162
|
+
def _parent(root_fd, rel, create=False):
|
|
163
|
+
parts = Path(rel).parts
|
|
164
|
+
if not parts or any(p in ('.', '..') for p in parts) or Path(rel).is_absolute():
|
|
165
|
+
raise OSError('Unsafe output path')
|
|
166
|
+
fd = os.dup(root_fd)
|
|
167
|
+
try:
|
|
168
|
+
for part in parts[:-1]:
|
|
169
|
+
if create:
|
|
170
|
+
try:
|
|
171
|
+
os.mkdir(part, 0o700, dir_fd=fd)
|
|
172
|
+
os.fsync(fd)
|
|
173
|
+
except FileExistsError:
|
|
174
|
+
pass
|
|
175
|
+
child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
|
|
176
|
+
os.close(fd)
|
|
177
|
+
fd = child
|
|
178
|
+
yield fd, parts[-1]
|
|
179
|
+
finally:
|
|
180
|
+
os.close(fd)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _write(root_fd, rel, body, *, exclusive=False, mode=0o600, expected=None):
|
|
184
|
+
with _parent(root_fd, rel, create=True) as (fd, name):
|
|
185
|
+
tmp = '.import-' + secrets.token_hex(16)
|
|
186
|
+
child = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=fd)
|
|
187
|
+
try:
|
|
188
|
+
with os.fdopen(child, 'wb') as stream:
|
|
189
|
+
os.fchmod(stream.fileno(), stat.S_IMODE(mode) & 0o777)
|
|
190
|
+
stream.write(body)
|
|
191
|
+
stream.flush()
|
|
192
|
+
os.fsync(stream.fileno())
|
|
193
|
+
guard = memory.file_lock(Path(name), parent_fd=fd) if rel in _MEMORY_FILES else nullcontext()
|
|
194
|
+
with guard:
|
|
195
|
+
if expected is not None and _read(fd, name) != expected:
|
|
196
|
+
raise OSError('Import destination changed during publication')
|
|
197
|
+
if exclusive:
|
|
198
|
+
os.link(tmp, name, src_dir_fd=fd, dst_dir_fd=fd, follow_symlinks=False)
|
|
199
|
+
else:
|
|
200
|
+
# Replacing a link cannot follow it. Never mutate the old inode in place.
|
|
201
|
+
os.rename(tmp, name, src_dir_fd=fd, dst_dir_fd=fd)
|
|
202
|
+
os.fsync(fd)
|
|
203
|
+
finally:
|
|
204
|
+
try:
|
|
205
|
+
os.unlink(tmp, dir_fd=fd)
|
|
206
|
+
except FileNotFoundError:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _ledger(root_fd, token, result=None):
|
|
211
|
+
name = _LEDGER + '/' + hashlib.sha256(token.encode()).hexdigest() + '.json'
|
|
212
|
+
if result is not None:
|
|
213
|
+
_write(root_fd, name, json.dumps(result).encode())
|
|
214
|
+
return result
|
|
215
|
+
try:
|
|
216
|
+
with _parent(root_fd, name) as (fd, leaf):
|
|
217
|
+
return json.loads(_read(fd, leaf)[0])
|
|
218
|
+
except FileNotFoundError:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _refresh_runtime(home):
|
|
223
|
+
from .commands import _reload_mcp, _reload_skills
|
|
224
|
+
try:
|
|
225
|
+
with homes.scoped_home(home, load_plugins=False):
|
|
226
|
+
_reload_skills()
|
|
227
|
+
_reload_mcp(log_errors=False)
|
|
228
|
+
return True
|
|
229
|
+
except Exception:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def run(ctx, params):
|
|
234
|
+
action, agent, token = params.get('action'), params.get('agent'), params.get('receipt')
|
|
235
|
+
if action not in ('discover', 'preview', 'apply', 'result') or action == 'preview' and agent not in _SOURCES or action in ('apply', 'result') and (not isinstance(token, str) or not 0 < len(token) <= 128):
|
|
236
|
+
raise InvalidParams('Invalid profile import request')
|
|
237
|
+
home = homes.profile_home_param(params)
|
|
238
|
+
with homes.scoped_home(home, load_plugins=False, read_only=True), plugin_catalog._lock:
|
|
239
|
+
try:
|
|
240
|
+
with _directory(home) as root_fd:
|
|
241
|
+
# Read the durable consumption record before plugin/source checks. A retry is readback only.
|
|
242
|
+
if action in ('apply', 'result'):
|
|
243
|
+
previous = _ledger(root_fd, token)
|
|
244
|
+
if previous is not None:
|
|
245
|
+
if 'expected' in previous:
|
|
246
|
+
files, _ = _snapshot(home, _TARGETS)
|
|
247
|
+
if all(name in files and hashlib.sha256(files[name]).hexdigest() == digest for name, digest in previous['expected'].items()):
|
|
248
|
+
return _ledger(root_fd, token, previous['confirmed'])
|
|
249
|
+
return previous['result']
|
|
250
|
+
return previous
|
|
251
|
+
if action == 'result':
|
|
252
|
+
# Fence a late apply that never reached the consumption record.
|
|
253
|
+
_PREVIEWS.pop(token, None)
|
|
254
|
+
return _result('changed', message='No import was confirmed. Preview again.')
|
|
255
|
+
try:
|
|
256
|
+
config.assert_config_writable(home)
|
|
257
|
+
except RpcError:
|
|
258
|
+
return _result('unavailable', message='Profile configuration is managed or its write policy cannot be verified. Import is unavailable.')
|
|
259
|
+
native = plugin_catalog.reviewed_importer(home)
|
|
260
|
+
if native is None:
|
|
261
|
+
return _result('unavailable', message='Install and enable Agent Importers from the plugin catalog.', catalog_required=True)
|
|
262
|
+
importer, sync, plugin = native
|
|
263
|
+
if action == 'discover':
|
|
264
|
+
sources = []
|
|
265
|
+
for name, (_, label) in _SOURCES.items():
|
|
266
|
+
try:
|
|
267
|
+
_source(name)
|
|
268
|
+
sources.append(dict(agent=name, label=label))
|
|
269
|
+
except OSError:
|
|
270
|
+
pass
|
|
271
|
+
return _result('available', sources=sources)
|
|
272
|
+
now = time.monotonic()
|
|
273
|
+
for old in list(_PREVIEWS):
|
|
274
|
+
if _PREVIEWS[old]['expires'] <= now:
|
|
275
|
+
del _PREVIEWS[old]
|
|
276
|
+
receipt = _PREVIEWS.get(token) if action == 'apply' else None
|
|
277
|
+
if action == 'apply' and (receipt is None or receipt['home'] != str(home)):
|
|
278
|
+
return _result('changed', message='Preview expired or belongs to another profile. Preview again.')
|
|
279
|
+
agent = receipt['agent'] if receipt else agent
|
|
280
|
+
source, source_files, source_identity = _source(agent)
|
|
281
|
+
if home == source or home in source.parents or source in home.parents:
|
|
282
|
+
raise OSError('Source overlaps the active profile')
|
|
283
|
+
target = _snapshot(home, _TARGETS)
|
|
284
|
+
identity = [str(home), str(source), plugin, _stamp((source_files, source_identity)), _stamp(target)]
|
|
285
|
+
if receipt and receipt['identity'] != identity:
|
|
286
|
+
return _result('changed', message='Source, profile or plugin changed. Preview again.')
|
|
287
|
+
if ctx is not None:
|
|
288
|
+
ctx.check_cancelled()
|
|
289
|
+
# Private temporary copies isolate the native path-based API from hostile path replacement.
|
|
290
|
+
with tempfile.TemporaryDirectory(prefix='talaria-import-') as temp:
|
|
291
|
+
stage_source, stage_target = Path(temp).resolve() / 'source', Path(temp).resolve() / 'target'
|
|
292
|
+
_materialize(stage_source, source_files, source_identity)
|
|
293
|
+
_materialize(stage_target, *target)
|
|
294
|
+
report = importer(agent, stage_source, stage_target, execute=False, overwrite=False).run()
|
|
295
|
+
items, stripped = _project(report)
|
|
296
|
+
plan = json.dumps([items, stripped], sort_keys=True)
|
|
297
|
+
if receipt and plan != receipt['plan']:
|
|
298
|
+
return _result('changed', message='Import plan changed. Preview again.')
|
|
299
|
+
if action == 'preview':
|
|
300
|
+
if not any(i['status'] == 'imported' for i in items):
|
|
301
|
+
return _result('preview', items=items, stripped=stripped)
|
|
302
|
+
token = secrets.token_urlsafe(32)
|
|
303
|
+
if len(_PREVIEWS) >= 64:
|
|
304
|
+
del _PREVIEWS[next(iter(_PREVIEWS))]
|
|
305
|
+
_PREVIEWS[token] = dict(home=str(home), agent=agent, identity=identity, plan=plan, expires=now + 300)
|
|
306
|
+
return _result('preview', items=items, stripped=stripped, receipt=token,
|
|
307
|
+
can_apply=any(i['status'] == 'imported' for i in items))
|
|
308
|
+
del _PREVIEWS[token]
|
|
309
|
+
unknown = _result('unknown', message='Import outcome is unknown. Check the profile before starting a new preview.')
|
|
310
|
+
# Fsync the consumed record before any native write. Restart or cancellation cannot replay it.
|
|
311
|
+
_ledger(root_fd, token, unknown)
|
|
312
|
+
try:
|
|
313
|
+
report = importer(agent, stage_source, stage_target, execute=True, overwrite=False).run()
|
|
314
|
+
items, stripped = _project(report)
|
|
315
|
+
sync.update_sync_manifest(agent, stage_source, stage_target, False, report,
|
|
316
|
+
refresh_digest=not any(i['status'] == 'error' for i in items))
|
|
317
|
+
manifest_path = stage_target / 'import-sync.json'
|
|
318
|
+
manifest = json.loads(manifest_path.read_text())
|
|
319
|
+
manifest['agents'][agent]['source'] = str(source)
|
|
320
|
+
manifest_path.write_text(json.dumps(manifest))
|
|
321
|
+
config.assert_config_writable(home)
|
|
322
|
+
output, output_identity = _snapshot(stage_target, _TARGETS)
|
|
323
|
+
# Check original objects again before publication; no write resolves a client/native path.
|
|
324
|
+
fresh_source = _source(agent)
|
|
325
|
+
if (plugin_catalog.reviewed_importer(home)[2] != plugin
|
|
326
|
+
or _stamp(fresh_source[1:]) != identity[3] or _stamp(_snapshot(home, _TARGETS)) != identity[4]):
|
|
327
|
+
return _ledger(root_fd, token, _result('changed', message='Import inputs changed before publication. Preview again.'))
|
|
328
|
+
result = _result('partial' if any(i['status'] == 'error' for i in items) else 'applied', items=items, stripped=stripped)
|
|
329
|
+
result['message'] = 'Files imported. Reload Skills and MCP before using the imported setup.'
|
|
330
|
+
_ledger(root_fd, token, dict(result=unknown, expected={k: hashlib.sha256(v).hexdigest() for k, v in output.items()}, confirmed=result))
|
|
331
|
+
for rel, body in output.items():
|
|
332
|
+
if target[0].get(rel) != body:
|
|
333
|
+
_write(root_fd, rel, body, exclusive=rel not in target[0], mode=output_identity[rel][2], expected=(target[0][rel], target[1][rel]) if rel in target[0] else None)
|
|
334
|
+
result = _result('partial' if any(i['status'] == 'error' for i in items) else 'applied', items=items, stripped=stripped)
|
|
335
|
+
if not _refresh_runtime(home):
|
|
336
|
+
result['message'] = 'Files imported. Reload Skills and MCP before using the imported setup.'
|
|
337
|
+
return _ledger(root_fd, token, result)
|
|
338
|
+
except Exception:
|
|
339
|
+
# Exceptions may include secret values. The durable unknown record is the only public result.
|
|
340
|
+
return unknown
|
|
341
|
+
except (OSError, ValueError, TypeError, KeyError):
|
|
342
|
+
return _result('unknown' if action == 'result' else 'changed' if action == 'apply' else 'unavailable', message='Import paths or state are unsafe, unavailable or changed. Check the profile before continuing.')
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def register(registry):
|
|
346
|
+
registry.method('profiles.import')(run)
|
|
@@ -18,6 +18,7 @@ import importlib.util
|
|
|
18
18
|
import inspect
|
|
19
19
|
import json
|
|
20
20
|
import logging
|
|
21
|
+
import math
|
|
21
22
|
import os
|
|
22
23
|
import queue
|
|
23
24
|
import re
|
|
@@ -34,7 +35,7 @@ from ..errors import InvalidParams, RpcError
|
|
|
34
35
|
from ..home import profile_home_param, scoped_home, session_workspace
|
|
35
36
|
from ..rpc import CallContext
|
|
36
37
|
from .checkpoints import checkpoint_kwargs
|
|
37
|
-
from .providers import runtime_failure
|
|
38
|
+
from .providers import require_runtime_ownership, runtime_failure
|
|
38
39
|
from .state_db import release_all, release_home, release_session_db
|
|
39
40
|
from .toolsets import disabled_toolsets
|
|
40
41
|
|
|
@@ -69,6 +70,12 @@ class _Run:
|
|
|
69
70
|
self.session_ended = False
|
|
70
71
|
self.home = None # the profile home its agent runs under, set by ``start``
|
|
71
72
|
self.mcp_discovery_outcome = "ok"
|
|
73
|
+
self.deadline: float | None = None
|
|
74
|
+
|
|
75
|
+
def prompt_cancelled(self, data: dict) -> bool:
|
|
76
|
+
# Vault answers racing a clear retain their existing unlock-then-release cleanup at turn end.
|
|
77
|
+
ended = self.session_ended and not str(data.get("kind") or "").startswith("vault_")
|
|
78
|
+
return self.ctx.cancelled or self.cancel.is_set() or self.finished.is_set() or ended
|
|
72
79
|
|
|
73
80
|
|
|
74
81
|
class _ClarifyEntry:
|
|
@@ -80,7 +87,77 @@ class _ClarifyEntry:
|
|
|
80
87
|
self.deadline: float | None = None
|
|
81
88
|
|
|
82
89
|
|
|
90
|
+
def _ask_clarify(run: _Run, data: dict, timeout: int, emit=None) -> str | None:
|
|
91
|
+
"""Park on a prompt the server relays until it is answered, the turn is cancelled or ends, or ``timeout``
|
|
92
|
+
passes (``<= 0``: no limit). The answer, or None, goes back to the Agent and is never emitted."""
|
|
93
|
+
emit = emit or run.ctx.emit
|
|
94
|
+
entry = _ClarifyEntry(data)
|
|
95
|
+
entry.data["clarify_id"] = entry.clarify_id
|
|
96
|
+
entry.deadline = None if timeout <= 0 else time.monotonic() + float(timeout)
|
|
97
|
+
if run.deadline is not None:
|
|
98
|
+
entry.deadline = min(entry.deadline, run.deadline) if entry.deadline is not None else run.deadline
|
|
99
|
+
with run.lock:
|
|
100
|
+
if run.prompt_cancelled(data) or (entry.deadline is not None and entry.deadline <= time.monotonic()):
|
|
101
|
+
return None
|
|
102
|
+
run.clarify_entries[entry.clarify_id] = entry
|
|
103
|
+
try:
|
|
104
|
+
emit("clarify", dict(entry.data))
|
|
105
|
+
while not entry.event.is_set():
|
|
106
|
+
if run.prompt_cancelled(data):
|
|
107
|
+
break
|
|
108
|
+
if entry.deadline is None:
|
|
109
|
+
entry.event.wait(0.1)
|
|
110
|
+
continue
|
|
111
|
+
remaining = entry.deadline - time.monotonic()
|
|
112
|
+
if remaining <= 0:
|
|
113
|
+
break
|
|
114
|
+
entry.event.wait(min(0.1, remaining))
|
|
115
|
+
finally:
|
|
116
|
+
with run.lock:
|
|
117
|
+
run.clarify_entries.pop(entry.clarify_id, None)
|
|
118
|
+
emit("clarify_resolved", {"clarify_id": entry.clarify_id, "session_id": run.session_id})
|
|
119
|
+
return None if run.prompt_cancelled(data) else entry.result
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _clarify_callback(run: _Run, params: dict, emit=None):
|
|
123
|
+
def clarify_callback(question, choices=None, multi_select=False, questions=None):
|
|
124
|
+
# TAL-351: remove the legacy signature path when v0.21.6 is the oldest supported Agent.
|
|
125
|
+
structured = isinstance(question, list)
|
|
126
|
+
if structured:
|
|
127
|
+
questions, question = question, ""
|
|
128
|
+
choices_list = [str(c) for c in (choices or [])]
|
|
129
|
+
# Predecessor `_clarify_timeout_seconds`: the Agent's own resolver over this profile's config
|
|
130
|
+
# (``clarify.timeout`` else ``agent.clarify_timeout`` else 3600); ``<= 0`` waits until answered or cancelled.
|
|
131
|
+
# The advertised ``timeout_seconds`` is what the clients count down, so it is the same number.
|
|
132
|
+
timeout = _clarify_timeout(params)
|
|
133
|
+
if run.deadline is not None:
|
|
134
|
+
remaining = max(1, math.ceil(run.deadline - time.monotonic()))
|
|
135
|
+
timeout = min(timeout, remaining) if timeout > 0 else remaining
|
|
136
|
+
data = {"question": str(question or ""), "choices_offered": choices_list, "session_id": run.session_id, "kind": "clarify", "requested_at": time.time(), "timeout_seconds": timeout}
|
|
137
|
+
if multi_select:
|
|
138
|
+
data["multi_select"] = True
|
|
139
|
+
# Batch prompts (the Agent's ``_run_batch``) carry no top-level question and expect the ``{"answers": {...}}`` reply.
|
|
140
|
+
if isinstance(questions, list) and questions:
|
|
141
|
+
data["questions"] = questions
|
|
142
|
+
answer = _ask_clarify(run, data, timeout, emit)
|
|
143
|
+
if answer is not None:
|
|
144
|
+
if structured:
|
|
145
|
+
return {"answers": json.loads(answer)["answers"], "outcome": "submitted"}
|
|
146
|
+
return answer
|
|
147
|
+
cancelled = run.prompt_cancelled(data)
|
|
148
|
+
if structured:
|
|
149
|
+
return {"answers": {}, "outcome": "cancelled" if cancelled else "timed_out",
|
|
150
|
+
"notice": "The user cancelled the clarification." if cancelled else "The clarification timed out."}
|
|
151
|
+
# Older tools have no outcome field. A batch understands timed_out; a single needs an empty answer.
|
|
152
|
+
if questions:
|
|
153
|
+
return json.dumps({"answers": {}, "timed_out": not cancelled})
|
|
154
|
+
return ""
|
|
155
|
+
|
|
156
|
+
return clarify_callback
|
|
157
|
+
|
|
158
|
+
|
|
83
159
|
_RUNS: dict[str, _Run] = {}
|
|
160
|
+
_COMMAND_RUNS: dict[str, _Run] = {}
|
|
84
161
|
_RUNS_BY_SESSION: dict[str, str] = {}
|
|
85
162
|
# Which run owns the Agent's gateway approval callback per session; register/unregister happen under one lock so a
|
|
86
163
|
# stale run can never remove a successor's callback, and a successor can never leak a predecessor's.
|
|
@@ -108,6 +185,43 @@ _MCP_REDISCOVERY_WAIT = 2.0
|
|
|
108
185
|
_MCP_PENDING_KEY = "mcp_discovery_pending"
|
|
109
186
|
|
|
110
187
|
|
|
188
|
+
def cancel_command_choices(session_id: str | None = None, home=None) -> None:
|
|
189
|
+
with _RUNS_LOCK:
|
|
190
|
+
for run in _COMMAND_RUNS.values():
|
|
191
|
+
if (session_id is None or run.session_id == session_id) and (home is None or run.home == os.path.realpath(home)):
|
|
192
|
+
run.session_ended = True
|
|
193
|
+
run.cancel.set()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@contextlib.contextmanager
|
|
197
|
+
def command_choices(ctx: CallContext, params: dict):
|
|
198
|
+
session_id = str(params.get("session_id") or "").strip()
|
|
199
|
+
try:
|
|
200
|
+
from hermes_cli.plugin_choices import bind_asker, clarify_asker
|
|
201
|
+
except ImportError:
|
|
202
|
+
yield
|
|
203
|
+
return
|
|
204
|
+
if not session_id:
|
|
205
|
+
yield
|
|
206
|
+
return
|
|
207
|
+
run = _Run(uuid.uuid4().hex, session_id, ctx)
|
|
208
|
+
# The server's commands.exec budget includes every question and the handler's final output.
|
|
209
|
+
run.deadline = time.monotonic() + 120
|
|
210
|
+
run.home = os.path.realpath(profile_home_param(params))
|
|
211
|
+
with _MCP_DISCOVERY_LOCK, _RUNS_LOCK:
|
|
212
|
+
ctx.check_cancelled()
|
|
213
|
+
if _DRAINING.is_set() or run.home in _MCP_CLOSING_HOMES:
|
|
214
|
+
raise RpcError("Command profile is unavailable", condition="profile_unavailable")
|
|
215
|
+
_COMMAND_RUNS[run.stream_id] = run
|
|
216
|
+
try:
|
|
217
|
+
with bind_asker(clarify_asker(_clarify_callback(run, params))):
|
|
218
|
+
yield
|
|
219
|
+
finally:
|
|
220
|
+
run.finished.set()
|
|
221
|
+
with _RUNS_LOCK:
|
|
222
|
+
_COMMAND_RUNS.pop(run.stream_id, None)
|
|
223
|
+
|
|
224
|
+
|
|
111
225
|
def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
|
|
112
226
|
"""Predecessor ``_tool_result_snippet``: the ``output``/``result``/``error`` of a dict (or JSON) result, hard-cut."""
|
|
113
227
|
if limit <= 0:
|
|
@@ -313,8 +427,14 @@ def _usage(agent: Any) -> dict:
|
|
|
313
427
|
value = getattr(agent, name, None)
|
|
314
428
|
return value if isinstance(value, (int, float)) else 0
|
|
315
429
|
|
|
430
|
+
try:
|
|
431
|
+
from gateway.session_usage import plan_gauge
|
|
432
|
+
gauge = plan_gauge(agent)
|
|
433
|
+
except Exception: # noqa: BLE001 - older Agent capability omission clears a complete snapshot
|
|
434
|
+
gauge = None
|
|
316
435
|
cost = getattr(agent, "session_estimated_cost_usd", None)
|
|
317
436
|
return {
|
|
437
|
+
**({"plan_gauge": gauge} if gauge is not None else {}),
|
|
318
438
|
"prompt_tokens": int(_num("session_prompt_tokens")),
|
|
319
439
|
"completion_tokens": int(_num("session_completion_tokens")),
|
|
320
440
|
"cache_read_tokens": int(_num("session_cache_read_tokens")),
|
|
@@ -695,6 +815,7 @@ def _cancel_checkpoint(run: _Run) -> list | None:
|
|
|
695
815
|
|
|
696
816
|
|
|
697
817
|
def _resolve_runtime(provider: str | None, model: str) -> dict:
|
|
818
|
+
require_runtime_ownership(provider)
|
|
698
819
|
try:
|
|
699
820
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
700
821
|
except Exception as exc: # noqa: BLE001
|
|
@@ -1363,6 +1484,7 @@ def _cancel_background_reviews(entries, message: str) -> list[threading.Thread]:
|
|
|
1363
1484
|
|
|
1364
1485
|
def evict_all_agents() -> int:
|
|
1365
1486
|
"""Drop every cached agent (credentials or environment changed underneath them)."""
|
|
1487
|
+
cancel_command_choices()
|
|
1366
1488
|
with _AGENT_CACHE_LOCK:
|
|
1367
1489
|
entries = list(_AGENT_CACHE.values())
|
|
1368
1490
|
_AGENT_CACHE.clear()
|
|
@@ -1387,7 +1509,7 @@ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
|
|
|
1387
1509
|
run.cancel.set()
|
|
1388
1510
|
while time.monotonic() < deadline:
|
|
1389
1511
|
with _AGENT_CACHE_LOCK, _RUNS_LOCK:
|
|
1390
|
-
if not _COMMITTING and not any(_RUNS.get(run.stream_id) is run for run in runs):
|
|
1512
|
+
if not _COMMITTING and not _COMMAND_RUNS and not any(_RUNS.get(run.stream_id) is run for run in runs):
|
|
1391
1513
|
break
|
|
1392
1514
|
time.sleep(0.05)
|
|
1393
1515
|
# A turn may have cached its agent after the first eviction.
|
|
@@ -1440,6 +1562,7 @@ def release_profile_agents(home):
|
|
|
1440
1562
|
raise RpcError("The profile is already being deleted.", condition="profile_unavailable")
|
|
1441
1563
|
_MCP_CLOSING_HOMES.add(target)
|
|
1442
1564
|
try:
|
|
1565
|
+
cancel_command_choices(home=home)
|
|
1443
1566
|
with _AGENT_CACHE_LOCK:
|
|
1444
1567
|
entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
|
|
1445
1568
|
started = _release_agents_locked(entries)
|
|
@@ -1470,8 +1593,8 @@ def release_profile_agents(home):
|
|
|
1470
1593
|
|
|
1471
1594
|
|
|
1472
1595
|
def _holding_home(target: str) -> bool:
|
|
1473
|
-
with _AGENT_CACHE_LOCK:
|
|
1474
|
-
return any(os.path.realpath(home) == target for _agent, home in _COMMITTING)
|
|
1596
|
+
with _AGENT_CACHE_LOCK, _RUNS_LOCK:
|
|
1597
|
+
return any(os.path.realpath(home) == target for _agent, home in _COMMITTING) or any(run.home == target for run in _COMMAND_RUNS.values())
|
|
1475
1598
|
|
|
1476
1599
|
|
|
1477
1600
|
def _acquire_session_db(home):
|
|
@@ -1889,28 +2012,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1889
2012
|
emit("notice_clear", {"key": str(key)})
|
|
1890
2013
|
|
|
1891
2014
|
def ask(data: dict, timeout: int) -> str | None:
|
|
1892
|
-
|
|
1893
|
-
passes (``<= 0``: no limit). The answer, or None, goes back to the Agent and is never emitted."""
|
|
1894
|
-
entry = _ClarifyEntry(data)
|
|
1895
|
-
entry.data["clarify_id"] = entry.clarify_id
|
|
1896
|
-
entry.deadline = None if timeout <= 0 else time.monotonic() + float(timeout)
|
|
1897
|
-
with run.lock:
|
|
1898
|
-
run.clarify_entries[entry.clarify_id] = entry
|
|
1899
|
-
emit("clarify", dict(entry.data))
|
|
1900
|
-
while not entry.event.is_set():
|
|
1901
|
-
if run.cancel.is_set() or run.finished.is_set() or (data.get("kind") == "skill_secret" and (ctx.cancelled or run.session_ended)):
|
|
1902
|
-
break
|
|
1903
|
-
if entry.deadline is None:
|
|
1904
|
-
entry.event.wait(1.0)
|
|
1905
|
-
continue
|
|
1906
|
-
remaining = entry.deadline - time.monotonic()
|
|
1907
|
-
if remaining <= 0:
|
|
1908
|
-
break
|
|
1909
|
-
entry.event.wait(min(1.0, remaining))
|
|
1910
|
-
with run.lock:
|
|
1911
|
-
run.clarify_entries.pop(entry.clarify_id, None)
|
|
1912
|
-
emit("clarify_resolved", {"clarify_id": entry.clarify_id, "session_id": session_id})
|
|
1913
|
-
return entry.result
|
|
2015
|
+
return _ask_clarify(run, data, timeout, emit)
|
|
1914
2016
|
|
|
1915
2017
|
def ask_vault(kind: str, fields: dict) -> str:
|
|
1916
2018
|
"""TAL-887: a password-manager prompt; an empty answer is the Agent's decline, as are expiry and cancel."""
|
|
@@ -1966,35 +2068,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
|
|
|
1966
2068
|
except Exception: # noqa: BLE001 - never expose an exception that could include the submitted value
|
|
1967
2069
|
return {**skipped, "success": False}
|
|
1968
2070
|
|
|
1969
|
-
|
|
1970
|
-
# TAL-351: remove the legacy signature path when v0.21.6 is the oldest supported Agent.
|
|
1971
|
-
structured = isinstance(question, list)
|
|
1972
|
-
if structured:
|
|
1973
|
-
questions, question = question, ""
|
|
1974
|
-
choices_list = [str(c) for c in (choices or [])]
|
|
1975
|
-
# Predecessor `_clarify_timeout_seconds`: the Agent's own resolver over this profile's config
|
|
1976
|
-
# (``clarify.timeout`` else ``agent.clarify_timeout`` else 3600); ``<= 0`` waits until answered or cancelled.
|
|
1977
|
-
# The advertised ``timeout_seconds`` is what the clients count down, so it is the same number.
|
|
1978
|
-
timeout = _clarify_timeout(params)
|
|
1979
|
-
data = {"question": str(question or ""), "choices_offered": choices_list, "session_id": session_id, "kind": "clarify", "requested_at": time.time(), "timeout_seconds": timeout}
|
|
1980
|
-
if multi_select:
|
|
1981
|
-
data["multi_select"] = True
|
|
1982
|
-
# Batch prompts (the Agent's ``_run_batch``) carry no top-level question and expect the ``{"answers": {...}}`` reply.
|
|
1983
|
-
if isinstance(questions, list) and questions:
|
|
1984
|
-
data["questions"] = questions
|
|
1985
|
-
answer = ask(data, timeout)
|
|
1986
|
-
if answer is not None:
|
|
1987
|
-
if structured:
|
|
1988
|
-
return {"answers": json.loads(answer)["answers"], "outcome": "submitted"}
|
|
1989
|
-
return answer
|
|
1990
|
-
cancelled = run.cancel.is_set() or run.finished.is_set()
|
|
1991
|
-
if structured:
|
|
1992
|
-
return {"answers": {}, "outcome": "cancelled" if cancelled else "timed_out",
|
|
1993
|
-
"notice": "The user cancelled the clarification." if cancelled else "The clarification timed out."}
|
|
1994
|
-
# Older tools have no outcome field. A batch understands timed_out; a single needs an empty answer.
|
|
1995
|
-
if questions:
|
|
1996
|
-
return json.dumps({"answers": {}, "timed_out": not cancelled})
|
|
1997
|
-
return ""
|
|
2071
|
+
clarify_callback = _clarify_callback(run, params, emit)
|
|
1998
2072
|
|
|
1999
2073
|
# TAL-818: a ``manage_connections`` card (a hosted connect, an MCP install) opened; the server turns the frame into
|
|
2000
2074
|
# the chat's connect card, which reads and answers the operation through ``connectors.operation`` / ``respond``.
|
|
@@ -2465,10 +2539,18 @@ def refine(params: dict) -> str:
|
|
|
2465
2539
|
return f"⚗ Reviewing this conversation in the background{tail} — any memory/skill updates will be reported when done."
|
|
2466
2540
|
|
|
2467
2541
|
|
|
2468
|
-
def _run_for(params: dict) -> _Run | None:
|
|
2542
|
+
def _run_for(params: dict, *, include_commands: bool = False) -> _Run | None:
|
|
2469
2543
|
stream_id = str(params.get("stream_id") or "").strip()
|
|
2470
2544
|
session_id = str(params.get("session_id") or "").strip()
|
|
2471
2545
|
with _RUNS_LOCK:
|
|
2546
|
+
clarify_id = str(params.get("clarify_id") or "")
|
|
2547
|
+
home = params.get("profile_home")
|
|
2548
|
+
if include_commands and clarify_id and session_id and home:
|
|
2549
|
+
for run in _COMMAND_RUNS.values():
|
|
2550
|
+
if run.session_id == session_id and run.home == os.path.realpath(home):
|
|
2551
|
+
with run.lock:
|
|
2552
|
+
if clarify_id in run.clarify_entries:
|
|
2553
|
+
return run
|
|
2472
2554
|
if stream_id:
|
|
2473
2555
|
return _RUNS.get(stream_id)
|
|
2474
2556
|
if session_id:
|
|
@@ -2619,6 +2701,7 @@ def register(registry) -> None:
|
|
|
2619
2701
|
in place."""
|
|
2620
2702
|
session_id = str(params.get("session_id") or "").strip()
|
|
2621
2703
|
end_session = params.get("clear_session") is True
|
|
2704
|
+
cancel_command_choices(session_id)
|
|
2622
2705
|
with _AGENT_CACHE_LOCK:
|
|
2623
2706
|
with _RUNS_LOCK:
|
|
2624
2707
|
run = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
|
|
@@ -2775,7 +2858,7 @@ def register(registry) -> None:
|
|
|
2775
2858
|
def clarify_respond_(ctx: CallContext, params: dict) -> dict:
|
|
2776
2859
|
response = str(params.get("response") or "")
|
|
2777
2860
|
clarify_id = str(params.get("clarify_id") or "").strip()
|
|
2778
|
-
run = _run_for(params)
|
|
2861
|
+
run = _run_for(params, include_commands=True)
|
|
2779
2862
|
if run is None:
|
|
2780
2863
|
return {"ok": False}
|
|
2781
2864
|
if params.get("session_id") and str(params["session_id"]) != run.session_id:
|
|
@@ -2786,7 +2869,7 @@ def register(registry) -> None:
|
|
|
2786
2869
|
if target is None or target.event.is_set() or (target.deadline is not None and time.monotonic() >= target.deadline):
|
|
2787
2870
|
return {"ok": False}
|
|
2788
2871
|
skill_secret = target.data.get("kind") == "skill_secret"
|
|
2789
|
-
if
|
|
2872
|
+
if ctx.cancelled or run.prompt_cancelled(target.data) or (skill_secret and not clarify_id):
|
|
2790
2873
|
return {"ok": False}
|
|
2791
2874
|
# Secure answers are as typed; an empty reply is their decline. Only the first answer releases a waiter.
|
|
2792
2875
|
secure = str(target.data.get("kind") or "").startswith("vault_") or skill_secret
|