@maudecode/talaria-web 1.13.0 → 1.15.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 +4 -4
- package/dist/api/automation-router.d.ts +102 -0
- package/dist/api/automation-router.js +2 -0
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +589 -52
- package/dist/api/chat-router.js +111 -121
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/router.d.ts +2 -1
- package/dist/api/router.js +3 -1
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +1918 -299
- package/dist/api/sessions-router.js +39 -24
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +45 -8
- package/dist/api/settings-router.js +14 -10
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +1 -1
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/app.d.ts +2728 -416
- package/dist/config/agent-config.js +20 -15
- package/dist/config/agent-config.js.map +1 -1
- package/dist/http/context.d.ts +4 -2
- package/dist/http/context.js.map +1 -1
- package/dist/onboarding.d.ts +1 -1
- package/dist/onboarding.js +2 -1
- package/dist/onboarding.js.map +1 -1
- package/dist/providers/catalog.d.ts +11 -0
- package/dist/providers/catalog.js +32 -1
- package/dist/providers/catalog.js.map +1 -1
- package/dist/runtime.js +56 -17
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +137 -0
- package/dist/sessions/background-tasks.js +349 -0
- package/dist/sessions/background-tasks.js.map +1 -0
- package/dist/sessions/background-updates.d.ts +56 -0
- package/dist/sessions/background-updates.js +152 -0
- package/dist/sessions/background-updates.js.map +1 -0
- package/dist/sessions/cli-sessions.d.ts +6 -0
- package/dist/sessions/cli-sessions.js +20 -7
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +17 -1
- package/dist/sessions/completions.js +83 -12
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +33 -0
- package/dist/sessions/compress.js +66 -0
- package/dist/sessions/compress.js.map +1 -0
- package/dist/sessions/journal.d.ts +27 -12
- package/dist/sessions/journal.js +115 -40
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +14 -0
- package/dist/sessions/list.js +89 -16
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +19 -1
- package/dist/sessions/merge.js +75 -7
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/relay.d.ts +17 -1
- package/dist/sessions/relay.js +74 -13
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +61 -3
- package/dist/sessions/service.js +244 -16
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +16 -0
- package/dist/sessions/session.js +23 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/source-kind.d.ts +22 -0
- package/dist/sessions/source-kind.js +54 -0
- package/dist/sessions/source-kind.js.map +1 -0
- package/dist/sessions/state-db.d.ts +10 -1
- package/dist/sessions/state-db.js +48 -17
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -0
- package/dist/sessions/store.js +23 -4
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +3 -0
- package/dist/sessions/streams.js +0 -5
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +4 -0
- package/dist/sessions/titles.js +16 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +1 -0
- package/dist/sessions/turn-context.js +3 -0
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +66 -6
- package/dist/sessions/turn.js +306 -63
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +6 -0
- package/dist/settings.js.map +1 -1
- package/dist/tools/crons.d.ts +34 -3
- package/dist/tools/crons.js +271 -31
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/hygiene.d.ts +11 -3
- package/dist/tools/hygiene.js +32 -14
- package/dist/tools/hygiene.js.map +1 -1
- package/package.json +2 -2
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/methods/chat.py +233 -6
- package/sidecar/talaria_sidecar/methods/cron.py +22 -14
- package/sidecar/talaria_sidecar/methods/process.py +318 -0
- package/static/dist/FILES.txt +120 -119
- package/static/dist/assets/AppShell-orat1s43.js +1 -0
- package/static/dist/assets/ChatPage-ojhka3hy.js +22 -0
- package/static/dist/assets/HubRoute-gxgw1t33.js +2 -0
- package/static/dist/assets/{Markdown-bx4n7qs4.js → Markdown-dy8t4dy7.js} +7 -7
- package/static/dist/assets/{RightPanel-ds4majj3.js → RightPanel-c66ani1s.js} +1 -1
- package/static/dist/assets/Select-fswf66up.js +1 -0
- package/static/dist/assets/SessionListPanel-goh5mtt8.js +1 -0
- package/static/dist/assets/{SettingsLayout-oigbg9rl.js → SettingsLayout-n5d4wq4m.js} +1 -1
- package/static/dist/assets/States-kxyu50at.js +1 -0
- package/static/dist/assets/Toaster-6zmbcqgg.js +1 -0
- package/static/dist/assets/_app-fowyruiu.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +1 -0
- package/static/dist/assets/_app.index-c9zv4y7w.js +1 -0
- package/static/dist/assets/_app.insights-jf12nofo.js +1 -0
- package/static/dist/assets/_app.kanban-lri5a4yu.js +1 -0
- package/static/dist/assets/_app.logs-md1p0zqo.js +1 -0
- package/static/dist/assets/_app.memory-bzvf2e28.js +1 -0
- package/static/dist/assets/_app.profiles-jpg7zmbn.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-ciix6itb.js → _app.session._sessionId-gyoh5ylx.js} +1 -1
- package/static/dist/assets/_app.settings-mpuwlx4j.js +1 -0
- package/static/dist/assets/_app.settings._section-knz8rwoj.js +1 -0
- package/static/dist/assets/_app.skills-fwu1dkir.js +1 -0
- package/static/dist/assets/_app.tasks-h9gphtl3.js +2 -0
- package/static/dist/assets/_app.todos-hd1dnvrh.js +1 -0
- package/static/dist/assets/_app.workspaces-l8toyn5f.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-krt0yaru.js → abnfDiagram-VCTEODGH-cjqletra.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-i54t9ywl.js → architectureDiagram-5GKGNRK7-k5a7v36n.js} +1 -1
- package/static/dist/assets/back-e4m6nsnm.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-fevbz4mz.js → blockDiagram-I7D4REHJ-b195z021.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-np8eclmc.js → c4Diagram-7LVT6UL2-olu212k1.js} +1 -1
- package/static/dist/assets/channel-gozy3dp3.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-owoi036m.js → chunk-4HAMMTFA-b8o1nufs.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-md44zf1n.js → chunk-75Z2AOVW-l1seddto.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-ka3rw8d6.js → chunk-DU6HZSFF-lo5zg15v.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-iasnjsky.js → chunk-F27PBJKO-dsdzrp16.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ex1up6tz.js → chunk-GMAD6QVW-f3fxqq8b.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-d4kmmg9p.js → chunk-GVQU2GXP-h8egxtf6.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-mwy42t85.js → chunk-IMKFNOWR-iolwd8s0.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-fv7ry213.js → chunk-L3NEJ4N5-b88bbtwi.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-jg9ir8ac.js → chunk-OSK3NFVY-e3e7nr6s.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-pe9ms3ua.js → chunk-P2QGCYS3-mdc46rt1.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-dimsjrn1.js → chunk-POPQ4Y6H-kqlllrrm.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-m4hv98dy.js → chunk-PWAF6VOD-he585k2x.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gkjgl6qe.js → chunk-SHT3W25Y-gh94y9a4.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-bc8qss5l.js → chunk-SVP7TREG-euxp9y7e.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-k9wzw38w.js → chunk-TICWLB2K-c8cxes1q.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +1 -0
- package/static/dist/assets/{copied-d5joolw7.js → copied-kq0u3ht7.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-hueklikk.js → cynefin-OW5HDTMX-lxd44ux4.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-e83o1wr1.js → cynefinDiagram-5FMLGOSQ-njpiwat8.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-ir1tj2ar.js → dagre-GXQ25YYZ-nvgy6yju.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-o5218p2q.js → diagram-S7CK7UJ4-g98nyv72.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-ciad4o1i.js → diagram-UQ7AKVKN-ovgm6lvv.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-oharqjbt.js → diagram-VSXAHHWV-pb2bn2e4.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-wkwy62iv.js → diagram-VX7I27RA-6j4mvfqo.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-bl252i7r.js → diagram-Z3DM3KII-dqmb982r.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-nm0rcly7.js → ebnfDiagram-PWID7BFC-ozbpzb6d.js} +1 -1
- package/static/dist/assets/endpoints-k8agnro8.js +9 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-m1w0vta2.js → erDiagram-RLTQ6QDP-hl7m8vua.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-czxo6iew.js → ganttDiagram-EL5Y4UJY-ompixqmh.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-ip9j1rph.js → gitGraph-4MIJSDKK-b6vxwp9q.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-kalxdole.js → gitGraphDiagram-WWUBYQGX-k98f57on.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-lgsj6fsk.js → highlighted-body-KPVGNVTW-cd3ff6ge.js} +1 -1
- package/static/dist/assets/index-nfy0ezs6.css +2 -0
- package/static/dist/assets/index-nqed3oum.js +11 -0
- package/static/dist/assets/{info-A6RAGUB7-fv4fbc5m.js → info-A6RAGUB7-hjcwjami.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-kinx5jsl.js → infoDiagram-27XIBGKW-fm06rj36.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-kozvlqdv.js → ishikawaDiagram-5VMMS53U-bxak4mwu.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-lgpouxoe.js → journeyDiagram-3NMN7TZE-khmihclr.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-b320wo7b.js → kanban-definition-UXKFOSKX-h1r32wdx.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-hy8doeye.js → lazyRouteComponent-b2eg0fws.js} +1 -1
- package/static/dist/assets/{line-gxfwyhjq.js → line-friyjof3.js} +1 -1
- package/static/dist/assets/link-m90ahx3n.js +1 -0
- package/static/dist/assets/login-bw7m2fzo.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-loxeqsu3.js → mermaid-parser.core-f1gvpwrt.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-edi534gc.js → mindmap-definition-YA3MSWOX-mt2oxwue.js} +1 -1
- package/static/dist/assets/onboarding-ret37j1e.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-gmb3yy71.js → packet-AYTQ26CC-jik4ixlf.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-fxy5j8cx.js → pegDiagram-XKGWAZYB-erxlv6cc.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-do2pm4s6.js → pie-WAS4IAKB-mnkoldfl.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-mgjvb365.js → pieDiagram-E7YTZNPT-i7lwrada.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-dhok6m1j.js → quadrantDiagram-AXDQQJYC-fr8ejdoy.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-n5giw3vb.js → radar-RG4KPBEZ-otarjte2.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-h6nfs9bj.js → railroad-74A4TZTK-lw362oto.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-lfwg92r4.js → railroadDiagram-O6MQD6OU-esyn75pw.js} +1 -1
- package/static/dist/assets/{refresh-cw-bg6oc499.js → refresh-cw-k66keho6.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-c5k5pg07.js → requirementDiagram-BXWQKSXE-cyhi5u29.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-o9auio4y.js → sankeyDiagram-P5KCCOFB-jljpbo0h.js} +1 -1
- package/static/dist/assets/{saved-j5wyhbik.js → saved-do49si3v.js} +1 -1
- package/static/dist/assets/{search-ipa17zlr.js → search-j5ki6a9o.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-pfgodnmk.js → sequenceDiagram-WJ2MYXX4-fz0dhlu7.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-fcfjf6l8.js → settings_section_extensions_title-b7lojn0o.js} +1 -1
- package/static/dist/assets/share._token-kg8w647b.js +2 -0
- package/static/dist/assets/{sliders-horizontal-bfrh4rch.js → sliders-horizontal-c4wya4dy.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-m1y2cr3q.js → stateDiagram-D77RDMKH-kdnithdx.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-hzai6fwo.js → swimlanes-42K2YHIH-esd6gmz3.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +8 -0
- package/static/dist/assets/tabBuild-bjmyvmn5.js +56 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-gwefy9gi.js → timeline-definition-24CTP7MA-g0qygccv.js} +1 -1
- package/static/dist/assets/todoStore-kjk5l0sy.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-7q91p0dn.js → treeView-Q6P3EWNA-ndchzkuu.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-occom39a.js → treemap-WGGIJYW6-hwjxj0zz.js} +1 -1
- package/static/dist/assets/{untitled-cgw2enra.js → untitled-mbr6tuhu.js} +1 -1
- package/static/dist/assets/useForm-ocxu460f.js +1 -0
- package/static/dist/assets/useLocale-fzy5jhg6.js +1 -0
- package/static/dist/assets/{useParams-gpbjxj8s.js → useParams-lo9u33d1.js} +1 -1
- package/static/dist/assets/{useSearch-bhdptos7.js → useSearch-lq1mr090.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-ew4bhldi.js → vennDiagram-4TSXK5OY-gubovd1r.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-njdacy0u.js → wardley-WFR3VGLG-ir3om65j.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-mkvxx7u9.js → wardleyDiagram-VM6X3IG4-ce0or1ie.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-8uriotdw.js → xychartDiagram-S5SC5T6Z-dexylcjg.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-irdqom6e.js +0 -1
- package/static/dist/assets/ChatPage-ldhqevjn.js +0 -20
- package/static/dist/assets/HubRoute-hq24vhj8.js +0 -2
- package/static/dist/assets/Select-gpqmt6x1.js +0 -1
- package/static/dist/assets/SessionListPanel-hkvipik4.js +0 -1
- package/static/dist/assets/States-fn4o2wdf.js +0 -1
- package/static/dist/assets/Toaster-cwnut53k.js +0 -1
- package/static/dist/assets/_app-h6ago6y6.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-erqnfff5.js +0 -1
- package/static/dist/assets/_app.index-cq2fxmda.js +0 -1
- package/static/dist/assets/_app.insights-nzhld9gu.js +0 -1
- package/static/dist/assets/_app.kanban-m5uggety.js +0 -1
- package/static/dist/assets/_app.logs-d2kk9lza.js +0 -1
- package/static/dist/assets/_app.memory-gcmop1e7.js +0 -1
- package/static/dist/assets/_app.profiles-e9b2my7x.js +0 -1
- package/static/dist/assets/_app.settings-kcjum3c1.js +0 -1
- package/static/dist/assets/_app.settings._section-bi4jjex0.js +0 -1
- package/static/dist/assets/_app.skills-e4e61l23.js +0 -1
- package/static/dist/assets/_app.tasks-f1evdhbl.js +0 -2
- package/static/dist/assets/_app.todos-o7ijtk42.js +0 -1
- package/static/dist/assets/_app.workspaces-o18voh0w.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-notdhmpv.js +0 -1
- package/static/dist/assets/channel-jpk1nas2.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-hld301gc.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-hld301gc.js +0 -1
- package/static/dist/assets/endpoints-d8n28yf3.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-eaqs19mj.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-eli5764n.js +0 -1
- package/static/dist/assets/index-l08181vr.js +0 -11
- package/static/dist/assets/index-pdtcl20t.css +0 -2
- package/static/dist/assets/link-ib4prsn5.js +0 -1
- package/static/dist/assets/login-llplsuey.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-f3scrlds.js +0 -1
- package/static/dist/assets/onboarding-n1xhlgym.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-d5gaaud9.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ihudnbec.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-lgpvhel7.js +0 -1
- package/static/dist/assets/share._token-i339gqj4.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hehvfv55.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-nfk9mtst.js +0 -8
- package/static/dist/assets/todoStore-fhvdsen0.js +0 -1
- package/static/dist/assets/useForm-jwbfla68.js +0 -1
- package/static/dist/assets/useLocale-gieo1fn7.js +0 -1
- package/static/dist/assets/useSelector-f2aak1ol.js +0 -1
package/dist/sessions/service.js
CHANGED
|
@@ -3,13 +3,17 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { rmSync } from 'node:fs';
|
|
4
4
|
import { buildActiveTurnToken, copyJson, redactSessionData, redactValue, stripPublicInternalFields, withSceneToolDisplay } from '../redact.js';
|
|
5
5
|
import { DraftVersionConflict, normalizeDraftVersion } from './drafts.js';
|
|
6
|
-
import { allSessions, buildSessionListPayload, isClaimableCliSource, isMessagingSessionRecord, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMessageText, sessionSearchPreview } from './list.js';
|
|
6
|
+
import { allSessions, buildSessionListPayload, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMessageText, sessionSearchPreview } from './list.js';
|
|
7
7
|
import { anchorSceneIntOrNull, hydrateAnchorActivityScenes, normalizeAnchorSceneMessageRef, readAnchorSceneRows, storeAnchorScene, withTurnIds } from './anchor.js';
|
|
8
8
|
import { isSafeSessionId, lastMessageTimestamp, Session, titleFrom } from './session.js';
|
|
9
9
|
import { SessionBusy, SessionNotFound, statSignature } from './store.js';
|
|
10
10
|
import { attachTodoState } from './todo.js';
|
|
11
11
|
import { stateDbSessionMessages, stateDbSessionRow, stateDbSessionSources } from './state-db.js';
|
|
12
|
-
import {
|
|
12
|
+
import { anchorMessageKey, anchorSummary, CompressionJobs, visibleMessagesForAnchor } from './compress.js';
|
|
13
|
+
import { SidecarError } from '../sidecar/client.js';
|
|
14
|
+
import { attachmentObjects, isContextCompressionMarker, mergeSessionMessagesAppendOnly, pendingUserRow, sanitizeMessagesForApi, withAttachmentObjects, withBodyExcerpts, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
|
|
15
|
+
import { withBackgroundUpdates } from './background-updates.js';
|
|
16
|
+
import { withBackgroundLinks } from './background-tasks.js';
|
|
13
17
|
import { messagesForLimitedPayload, messageWindowForDisplay, MAX_MSG_LIMIT, parseMsgLimit, toolCallsForMessageWindow } from './window.js';
|
|
14
18
|
import { redactText } from '../redact.js';
|
|
15
19
|
import { buildShareSnapshot } from './shares.js';
|
|
@@ -25,11 +29,32 @@ export class HttpFailure extends Error {
|
|
|
25
29
|
this.name = 'HttpFailure';
|
|
26
30
|
}
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Python `_agent_runtime_barrier_response`: a stale local Agent checkout is refused with a typed 409 before the caller
|
|
34
|
+
* mutates anything. Any other failure of the check is not a verdict and lets the caller proceed.
|
|
35
|
+
*/
|
|
36
|
+
export async function ensureAgentRuntimeCurrent(sidecar) {
|
|
37
|
+
if (!sidecar)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
await sidecar.call('runtime.ensure_current', {});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof SidecarError && error.condition === 'agent_runtime_stale')
|
|
44
|
+
throw staleRuntimeFailure(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function staleRuntimeFailure(error) {
|
|
48
|
+
return new HttpFailure(409, error.message, { type: 'agent_runtime_stale', retryable: true, restart_scheduled: false, ...(error.data.agent_update_state !== undefined ? { agent_update_state: error.data.agent_update_state } : {}) });
|
|
49
|
+
}
|
|
28
50
|
const isDict = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
|
|
29
51
|
export class SessionService {
|
|
30
52
|
deps;
|
|
53
|
+
/** TAL-255: one manual compression worker per session, shared by every compress route. */
|
|
54
|
+
compressionJobs;
|
|
31
55
|
constructor(deps) {
|
|
32
56
|
this.deps = deps;
|
|
57
|
+
this.compressionJobs = new CompressionJobs();
|
|
33
58
|
}
|
|
34
59
|
get store() { return this.deps.store; }
|
|
35
60
|
publish(reason, profile, sessionId) {
|
|
@@ -135,16 +160,24 @@ export class SessionService {
|
|
|
135
160
|
* rows (a WebUI conversation continued from the CLI shows the CLI turns). This is the coordinate space `GET
|
|
136
161
|
* /api/session` exposes, so branching and the next model history slice/extend the same list.
|
|
137
162
|
*/
|
|
138
|
-
mergedTranscript(s, local = s.messages) {
|
|
139
|
-
const stateRows = this.stateDbRows(s);
|
|
163
|
+
mergedTranscript(s, local = s.messages, stateRows = this.stateDbRows(s)) {
|
|
140
164
|
if (!stateRows.length)
|
|
141
165
|
return local;
|
|
142
166
|
return mergeSessionMessagesAppendOnly(local, stateRows, { truncationWatermark: s.truncation_watermark });
|
|
143
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Python `reconciled_state_db_messages_for_session(prefer_context=True)`: the model history is the owner context
|
|
170
|
+
* extended append-only with the Agent's state.db rows (a CLI continuation of this session reaches the model), except
|
|
171
|
+
* for a compressed context whose anchor cannot be verified — that stays context-only.
|
|
172
|
+
*/
|
|
173
|
+
modelContext(s, stateRows) {
|
|
174
|
+
const local = s.context_messages.length ? s.context_messages : s.messages.filter((m) => !m._error && !m._partial);
|
|
175
|
+
return local.some((m) => isContextCompressionMarker(m)) ? local : this.mergedTranscript(s, local, stateRows ?? this.stateDbRows(s));
|
|
176
|
+
}
|
|
144
177
|
/** Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile. */
|
|
145
178
|
lookupCliMeta(sid) {
|
|
146
179
|
try {
|
|
147
|
-
return this.deps.cliSessions(this.deps.activeProfile(), { sourceFilter: null }).find((r) => str(r.session_id) === sid) ?? null;
|
|
180
|
+
return this.deps.cliSessions(this.deps.activeProfile(), { sourceFilter: null }).rows.find((r) => str(r.session_id) === sid) ?? null;
|
|
148
181
|
}
|
|
149
182
|
catch {
|
|
150
183
|
return null;
|
|
@@ -214,6 +247,10 @@ export class SessionService {
|
|
|
214
247
|
messages: msgs, created_at: Number(meta.created_at) || 0, updated_at: Number(meta.updated_at) || 0, profile: str(meta.profile) || null,
|
|
215
248
|
is_cli_session: claimable ? true : !subagentChild, source_tag: str(meta.source_tag) || null, raw_source: str(meta.raw_source) || null,
|
|
216
249
|
session_source: str(meta.session_source) || null, source_label: str(meta.source_label) || null, read_only: !claimable,
|
|
250
|
+
// Python `import_cli_session`: the claimed sidecar keeps the row's lineage, background project and channel identity
|
|
251
|
+
// (identity persists through `Session.extra`).
|
|
252
|
+
parent_session_id: str(meta.parent_session_id) || null, project_id: str(meta.project_id) || null,
|
|
253
|
+
...Object.fromEntries(CLI_IDENTITY_FIELDS.filter((k) => meta[k] != null && meta[k] !== '').map((k) => [k, meta[k]])),
|
|
217
254
|
}, defaults);
|
|
218
255
|
return { session, reason: claimable ? 'materialized' : 'not_claimable' };
|
|
219
256
|
}
|
|
@@ -233,17 +270,25 @@ export class SessionService {
|
|
|
233
270
|
if (this.isSubagentViewOnly(sid))
|
|
234
271
|
throw new HttpFailure(400, `Subagent sessions are view-only and cannot be ${verb} from WebUI`);
|
|
235
272
|
}
|
|
273
|
+
/** TAL-372: each delegation row of a turn's scene carries the status of the work it started. */
|
|
274
|
+
backgroundLinked(s, messages) {
|
|
275
|
+
return withBackgroundLinks(messages, this.deps.backgroundReceipts?.(s.session_id) ?? []);
|
|
276
|
+
}
|
|
236
277
|
/** `compact()` plus messages, redacted for the wire (Python `_public_session_projection`). */
|
|
237
278
|
publicSession(s, withMessages = true) {
|
|
238
279
|
const payload = this.wireRow(s);
|
|
239
280
|
// Mutation replies replace a client's transcript, so they carry the same server-built scenes as the detail.
|
|
240
281
|
if (withMessages)
|
|
241
|
-
payload.messages = hydrateAnchorActivityScenes(withToolCallOutcomes(withTurnIds(s.messages), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id });
|
|
282
|
+
payload.messages = this.backgroundLinked(s, hydrateAnchorActivityScenes(withToolCallOutcomes(withBackgroundUpdates(withTurnIds(s.messages), s), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id }));
|
|
242
283
|
return redactSessionData(payload, this.deps.redactEnabled());
|
|
243
284
|
}
|
|
244
285
|
/** `compact()` with the wire streaming/read-only flags (TAL-312), for replies that return the session row. */
|
|
245
286
|
wireRow(s) {
|
|
246
|
-
return withSessionWireFlags({ ...s.compact(), read_only: this.isReadOnly(s) }, this.deps.runtime.activeStreamIds);
|
|
287
|
+
return withSessionWireFlags({ ...s.compact({ contextLengthFor: this.deps.contextLengthFor }), read_only: this.isReadOnly(s), assistant_name: this.assistantName(s) }, this.deps.runtime.activeStreamIds);
|
|
288
|
+
}
|
|
289
|
+
/** The agent's display name for a session's profile (`assistant_name`). */
|
|
290
|
+
assistantName(s) {
|
|
291
|
+
return this.deps.assistantName(s.profile);
|
|
247
292
|
}
|
|
248
293
|
/** Python `public_session_projection(s.__dict__)`: every persisted field, redacted (session export). */
|
|
249
294
|
publicSessionDocument(s) {
|
|
@@ -283,7 +328,7 @@ export class SessionService {
|
|
|
283
328
|
if (journaled)
|
|
284
329
|
transcript = withoutRunningTurnOutput(transcript, { ...journaled, localCount: s.messages.length });
|
|
285
330
|
// Turn ids, tool outcomes and scenes are computed over the full transcript, so every window reports the same values.
|
|
286
|
-
const all = loadMessages ? hydrateAnchorActivityScenes(withToolCallOutcomes(withTurnIds(transcript), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: msgLimit !== null }) : [];
|
|
331
|
+
const all = loadMessages ? withBodyExcerpts(this.backgroundLinked(s, hydrateAnchorActivityScenes(withToolCallOutcomes(withBackgroundUpdates(withTurnIds(withAttachmentObjects(transcript)), s), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: msgLimit !== null })), s.active_stream_id) : [];
|
|
287
332
|
let truncated = [];
|
|
288
333
|
let offset = 0;
|
|
289
334
|
let summaryCount = null;
|
|
@@ -317,12 +362,12 @@ export class SessionService {
|
|
|
317
362
|
}
|
|
318
363
|
const activeStreamIds = this.deps.runtime.activeStreamIds;
|
|
319
364
|
const raw = {
|
|
320
|
-
...s.compact({ includeRuntime: true, activeStreamIds }),
|
|
365
|
+
...s.compact({ includeRuntime: true, activeStreamIds, contextLengthFor: this.deps.contextLengthFor }),
|
|
321
366
|
messages: truncated,
|
|
322
367
|
message_count: mergedCount,
|
|
323
368
|
tool_calls: toolCalls,
|
|
324
369
|
pending_user_message: s.pending_user_message,
|
|
325
|
-
pending_attachments: loadMessages ? s.pending_attachments : [],
|
|
370
|
+
pending_attachments: loadMessages ? attachmentObjects(s.pending_attachments) : [],
|
|
326
371
|
pending_started_at: s.pending_started_at,
|
|
327
372
|
pending_user_source: s.pending_user_source,
|
|
328
373
|
context_length: Number(s.context_length ?? 0) || this.deps.contextLengthFor(s.model, s.model_provider) || 0,
|
|
@@ -344,7 +389,9 @@ export class SessionService {
|
|
|
344
389
|
const revisionAfter = this.loadRevision(s);
|
|
345
390
|
raw._load_revision = revisionBefore !== null && revisionBefore === revisionAfter ? hashRevision(revisionBefore) : `unstable-${randomUUID().replace(/-/g, '')}`;
|
|
346
391
|
raw.read_only = this.isReadOnly(s);
|
|
392
|
+
raw.assistant_name = this.assistantName(s);
|
|
347
393
|
withSessionWireFlags(raw, activeStreamIds);
|
|
394
|
+
raw.pending_steers = raw.active_stream_id ? (this.deps.runtime.pendingSteers?.(str(raw.active_stream_id)) ?? []) : [];
|
|
348
395
|
return redactSessionData(raw, this.deps.redactEnabled());
|
|
349
396
|
}
|
|
350
397
|
/**
|
|
@@ -398,6 +445,7 @@ export class SessionService {
|
|
|
398
445
|
pinned: synth.pinned, archived: synth.archived, project_id: synth.project_id ?? null, profile: synth.profile,
|
|
399
446
|
is_cli_session: synth.is_cli_session, source_tag: synth.source_tag, raw_source: synth.raw_source, session_source: synth.session_source,
|
|
400
447
|
source_label: synth.source_label, read_only: synth.read_only, can_duplicate: false, messages: msgs, tool_calls: [], transcript_seq: null,
|
|
448
|
+
assistant_name: this.assistantName(synth),
|
|
401
449
|
};
|
|
402
450
|
attachTodoState(sess, msgs);
|
|
403
451
|
const merged = withSessionWireFlags(meta ? mergeCliSidebarMetadata(sess, meta) : sess, this.deps.runtime.activeStreamIds);
|
|
@@ -413,7 +461,7 @@ export class SessionService {
|
|
|
413
461
|
if (this.deps.runtime.activeRunStream(session.session_id))
|
|
414
462
|
return false;
|
|
415
463
|
const pendingAge = session.pending_started_at ? this.deps.now() - session.pending_started_at : null;
|
|
416
|
-
if (session.
|
|
464
|
+
if (session.hasPendingPrompt && pendingAge !== null && pendingAge < 30)
|
|
417
465
|
return false;
|
|
418
466
|
let target = session;
|
|
419
467
|
if (session.loadedMetadataOnly) {
|
|
@@ -433,7 +481,7 @@ export class SessionService {
|
|
|
433
481
|
// Python `_materialize_pending_user_turn_before_error` (#1361): the prompt that was in flight becomes a durable user
|
|
434
482
|
// turn and an interruption marker follows it, so a dead stream never silently drops what the user sent.
|
|
435
483
|
const pendingText = str(target.pending_user_message);
|
|
436
|
-
if (
|
|
484
|
+
if (target.hasPendingPrompt) {
|
|
437
485
|
const turnId = str(target.active_stream_id);
|
|
438
486
|
const startedAt = typeof target.pending_started_at === 'number' && target.pending_started_at > 0 ? target.pending_started_at : this.deps.now();
|
|
439
487
|
const attachments = [...target.pending_attachments];
|
|
@@ -466,9 +514,11 @@ export class SessionService {
|
|
|
466
514
|
const activeProfile = this.deps.activeProfile();
|
|
467
515
|
const wantState = params.showCliSessions || params.showCronSessions || params.showWebhookSessions || params.showKanbanSessions;
|
|
468
516
|
// Python reads every profile's state.db under all_profiles; this port projects the active profile only.
|
|
469
|
-
const
|
|
517
|
+
const cliRead = wantState ? this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
|
|
518
|
+
const cliRows = cliRead?.rows;
|
|
470
519
|
const gatewayIdentity = loadGatewaySessionIdentityMap(join(this.deps.profileHome(activeProfile), 'sessions', 'sessions.json'));
|
|
471
|
-
const
|
|
520
|
+
const truncatedSources = cliRead?.truncated;
|
|
521
|
+
const payload = buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), gatewayIdentity, stateDbSources: this.stateDbSources, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch });
|
|
472
522
|
return sessionListResponse(payload, this.deps.runtime, this.deps.redactEnabled(), this.deps.now());
|
|
473
523
|
}
|
|
474
524
|
search(q, opts) {
|
|
@@ -535,10 +585,10 @@ export class SessionService {
|
|
|
535
585
|
catch {
|
|
536
586
|
hermesHome = '';
|
|
537
587
|
}
|
|
538
|
-
const { is_streaming: streaming, active_stream_id: live, read_only: readOnly } = this.wireRow(full);
|
|
588
|
+
const { is_streaming: streaming, active_stream_id: live, read_only: readOnly, active_turn_origin: origin } = this.wireRow(full);
|
|
539
589
|
return {
|
|
540
590
|
session_id: full.session_id, title: full.title, model: full.model, profile, hermes_home: hermesHome, workspace: full.workspace, personality: full.personality,
|
|
541
|
-
message_count: full.messages.length, created_at: full.created_at, updated_at: full.updated_at, agent_running: streaming, is_streaming: streaming, active_stream_id: live, read_only: readOnly,
|
|
591
|
+
message_count: full.messages.length, created_at: full.created_at, updated_at: full.updated_at, agent_running: streaming, is_streaming: streaming, active_stream_id: live, active_turn_origin: origin, read_only: readOnly,
|
|
542
592
|
input_tokens: inp, output_tokens: out, total_tokens: inp + out, estimated_cost: full.estimated_cost,
|
|
543
593
|
};
|
|
544
594
|
}
|
|
@@ -774,6 +824,175 @@ export class SessionService {
|
|
|
774
824
|
this.deps.runtime.evictAgent(sid);
|
|
775
825
|
return { ok: true, session: this.publicSession(s) };
|
|
776
826
|
}
|
|
827
|
+
// ── manual compression (TAL-255) ────────────────────────────────────────
|
|
828
|
+
/**
|
|
829
|
+
* Python `_handle_session_compress_start`: validate, then join the session's running job or start a fresh one (a
|
|
830
|
+
* finished job is replaced). The stale-runtime refusal comes before a job exists; a running job is joined without it.
|
|
831
|
+
*/
|
|
832
|
+
async startCompression(sid, focusRaw) {
|
|
833
|
+
const running = () => { const job = this.compressionJobs.get(sid); return job?.status === 'running' ? job : undefined; };
|
|
834
|
+
// A running job is joined first: once it has installed its short context, the guards below would refuse a new one.
|
|
835
|
+
const existing = running();
|
|
836
|
+
if (existing)
|
|
837
|
+
return existing;
|
|
838
|
+
const { s } = this.compressionTarget(sid);
|
|
839
|
+
const focusTopic = str(focusRaw).trim().slice(0, 500) || null;
|
|
840
|
+
await ensureAgentRuntimeCurrent(this.deps.sidecar?.() ?? null);
|
|
841
|
+
// Another start may have admitted a job while the runtime check awaited.
|
|
842
|
+
const admitted = running();
|
|
843
|
+
if (admitted)
|
|
844
|
+
return admitted;
|
|
845
|
+
// The detached job (and its finalize) is the profile's activity until it settles; checked and taken in one step.
|
|
846
|
+
const profile = s.profile ?? null;
|
|
847
|
+
if (this.deps.profileDeleting?.(profile))
|
|
848
|
+
throw new HttpFailure(409, `Profile '${str(profile)}' is being deleted.`);
|
|
849
|
+
const release = this.deps.profileActivity?.(profile);
|
|
850
|
+
const now = this.deps.now();
|
|
851
|
+
const job = { session_id: sid, focus_topic: focusTopic, status: 'running', started_at: now, updated_at: now, done: Promise.resolve() };
|
|
852
|
+
job.done = this.compressSession(sid, focusTopic).finally(() => { release?.(); this.compressionJobs.expireLater(job); }).then((result) => { Object.assign(job, { status: 'done', result, updated_at: this.deps.now() }); }, (error) => {
|
|
853
|
+
const known = error instanceof HttpFailure;
|
|
854
|
+
if (!known)
|
|
855
|
+
this.deps.log(`[webui] Manual compression worker failed for session ${sid}: ${error.message}`);
|
|
856
|
+
Object.assign(job, { status: 'error', error: known ? error.message : `Compression failed: ${sanitizePaths(error)}`, error_status: known ? error.status : 500, error_extra: known ? error.extra : {}, updated_at: this.deps.now() });
|
|
857
|
+
});
|
|
858
|
+
this.compressionJobs.set(job);
|
|
859
|
+
return job;
|
|
860
|
+
}
|
|
861
|
+
/** The session a compression may run on now, and the model history it compresses: the context a turn would send, so a
|
|
862
|
+
* repeat compression builds on the previous summary instead of re-reading the whole display transcript. */
|
|
863
|
+
compressionTarget(sid) {
|
|
864
|
+
this.rejectSubagent(sid, 'compressed');
|
|
865
|
+
const s = this.mutationTarget(sid, 'compressed');
|
|
866
|
+
if (s.active_stream_id)
|
|
867
|
+
throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
|
|
868
|
+
const history = sanitizeMessagesForApi(this.modelContext(s));
|
|
869
|
+
if (history.length < 4)
|
|
870
|
+
throw new HttpFailure(400, 'Not enough conversation to compress (need at least 4 messages).');
|
|
871
|
+
return { s, history };
|
|
872
|
+
}
|
|
873
|
+
/** The session's compression job; a deleted session's finished result is dropped rather than served. */
|
|
874
|
+
compressionJob(sid) {
|
|
875
|
+
try {
|
|
876
|
+
this.store.get(sid, { metadataOnly: true });
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
this.compressionJobs.delete(sid);
|
|
880
|
+
throw new HttpFailure(404, 'Session not found');
|
|
881
|
+
}
|
|
882
|
+
return this.compressionJobs.get(sid);
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Python `_handle_session_compress`: compress the sanitized transcript in the sidecar outside the lock, then under the
|
|
886
|
+
* session lock refuse a result the session moved past (stream state or transcript changed) and install it as the
|
|
887
|
+
* model context with the manual anchor and the #4836 boundary that keeps state.db from replaying compressed rows.
|
|
888
|
+
* The guards run again first: a stream may have started while the caller awaited the runtime check.
|
|
889
|
+
*/
|
|
890
|
+
async compressSession(sid, focusTopic) {
|
|
891
|
+
const { s, history } = this.compressionTarget(sid);
|
|
892
|
+
// The display transcript and the model context must both be where the compression found them; the key starts from
|
|
893
|
+
// the exact history sent, so a state.db row that lands after this read fails the commit instead of being dropped.
|
|
894
|
+
const transcriptKey = (x, context) => JSON.stringify([sanitizeMessagesForApi(x.messages), context]);
|
|
895
|
+
const historyKey = transcriptKey(s, history);
|
|
896
|
+
const streamState = (x) => JSON.stringify([x.active_stream_id ?? null, x.pending_user_message ?? null, x.pending_attachments ?? null, x.pending_started_at ?? null]);
|
|
897
|
+
const streamBefore = streamState(s);
|
|
898
|
+
const sidecar = this.deps.sidecar?.();
|
|
899
|
+
if (!sidecar)
|
|
900
|
+
throw new HttpFailure(400, 'Compression failed: the Agent sidecar is not running');
|
|
901
|
+
const [model, provider] = this.deps.modelStateFromRequest(s.model, s.model_provider, s.model_provider);
|
|
902
|
+
let result;
|
|
903
|
+
try {
|
|
904
|
+
result = await sidecar.call('chat.compress', {
|
|
905
|
+
profile_home: this.deps.profileHome(s.profile ?? this.deps.activeProfile()), session_id: sid, model: model ?? '', model_provider: provider,
|
|
906
|
+
conversation_history: history, focus_topic: focusTopic, enabled_toolsets: s.enabled_toolsets,
|
|
907
|
+
}, { timeoutMs: 0 });
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
if (error instanceof SidecarError && error.condition === 'agent_runtime_stale')
|
|
911
|
+
throw staleRuntimeFailure(error);
|
|
912
|
+
if (error instanceof SidecarError && error.condition === 'credential_missing')
|
|
913
|
+
throw new HttpFailure(400, 'No provider configured -- cannot compress.');
|
|
914
|
+
throw new HttpFailure(400, `Compression failed: ${sanitizePaths(error)}`);
|
|
915
|
+
}
|
|
916
|
+
// A held compression lock is a conflict to retry; `nothing_to_do` is the Agent's verdict on this transcript.
|
|
917
|
+
if (result.status !== 'compressed')
|
|
918
|
+
throw new HttpFailure(result.status === 'lock_skipped' ? 409 : 400, result.message ?? 'Nothing to compress yet.');
|
|
919
|
+
const summary = result.summary ?? {};
|
|
920
|
+
let committed = false;
|
|
921
|
+
try {
|
|
922
|
+
const current = await this.store.withLock(sid, () => {
|
|
923
|
+
let live;
|
|
924
|
+
try {
|
|
925
|
+
live = this.store.get(sid);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
throw new HttpFailure(404, 'Session not found');
|
|
929
|
+
}
|
|
930
|
+
if (streamState(live) !== streamBefore)
|
|
931
|
+
throw new HttpFailure(409, 'Session stream state changed during compression; please retry.');
|
|
932
|
+
// One state.db read serves the check, the kept display rows, and the boundary.
|
|
933
|
+
const stateRows = this.stateDbRows(live);
|
|
934
|
+
if (transcriptKey(live, sanitizeMessagesForApi(this.modelContext(live, stateRows))) !== historyKey)
|
|
935
|
+
throw new HttpFailure(409, 'Session was modified during compression; please retry.');
|
|
936
|
+
// Rows the sidecar returns without a timestamp take the newest one this compression saw, never the clock: the
|
|
937
|
+
// boundary then hides no state.db row the append-only merge would still show (it already drops rows at or
|
|
938
|
+
// before the newest local row), so a CLI row committed after the read follows the compressed context.
|
|
939
|
+
const seen = [...live.messages, ...live.context_messages, ...stateRows].map((m) => Number(m.timestamp)).filter(Number.isFinite);
|
|
940
|
+
const stamp = seen.length ? Math.max(...seen) : this.deps.now();
|
|
941
|
+
const compressed = copyJson(result.messages);
|
|
942
|
+
for (const m of compressed)
|
|
943
|
+
m.timestamp ??= stamp;
|
|
944
|
+
live.context_messages = compressed;
|
|
945
|
+
live.active_stream_id = null;
|
|
946
|
+
live.pending_user_message = null;
|
|
947
|
+
live.pending_attachments = [];
|
|
948
|
+
live.pending_started_at = null;
|
|
949
|
+
live.pending_user_source = null;
|
|
950
|
+
// The compressed history included any state.db-only continuation; the new boundary would hide those rows from
|
|
951
|
+
// the display, so the transcript keeps them before it is applied (and the anchor counts them).
|
|
952
|
+
const display = this.mergedTranscript(live, live.messages, stateRows);
|
|
953
|
+
if (display.length > live.messages.length)
|
|
954
|
+
live.messages = copyJson(display);
|
|
955
|
+
const visible = visibleMessagesForAnchor(live.messages);
|
|
956
|
+
live.compression_anchor_visible_idx = visible.length ? visible.length - 1 : null;
|
|
957
|
+
live.compression_anchor_message_key = anchorMessageKey(visible.at(-1));
|
|
958
|
+
live.compression_anchor_summary = anchorSummary(summary, compressed);
|
|
959
|
+
live.compression_anchor_mode = 'manual';
|
|
960
|
+
// #4836: an intentional-shrink boundary, so append-only state.db reconciliation does not replay compressed rows.
|
|
961
|
+
live.truncation_watermark = truncationWatermarkFor(compressed);
|
|
962
|
+
live.truncation_boundary = live.truncation_watermark;
|
|
963
|
+
live.last_prompt_tokens = result.after_tokens;
|
|
964
|
+
live.post_compression_context_tokens_estimate = result.after_tokens;
|
|
965
|
+
this.store.save(live);
|
|
966
|
+
// A backup from before the compression would restore the uncompressed context.
|
|
967
|
+
try {
|
|
968
|
+
rmSync(`${this.store.pathFor(sid)}.bak`, { force: true });
|
|
969
|
+
}
|
|
970
|
+
catch { /* ignore */ }
|
|
971
|
+
return live;
|
|
972
|
+
});
|
|
973
|
+
committed = true;
|
|
974
|
+
// The cached turn agent still carries the uncompressed state; the next turn builds a fresh one (gateway parity).
|
|
975
|
+
// Awaited, so a turn sent right after the reply cannot reuse the old agent.
|
|
976
|
+
try {
|
|
977
|
+
await sidecar.call('chat.evict_agent', { session_id: sid });
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
this.deps.log(`[webui] agent eviction after compression of ${sid} failed: ${error.message}`);
|
|
981
|
+
}
|
|
982
|
+
return { ok: true, session: this.publicSession(current), summary, focus_topic: focusTopic };
|
|
983
|
+
}
|
|
984
|
+
finally {
|
|
985
|
+
// Second phase: the Agent's context-engine notification fires only for a result the session now holds.
|
|
986
|
+
if (result.commit_token) {
|
|
987
|
+
try {
|
|
988
|
+
await sidecar.call('chat.compress_finalize', { commit_token: result.commit_token, committed });
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
this.deps.log(`[webui] compression finalize for ${sid} failed: ${error.message}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
777
996
|
async clear(sid) {
|
|
778
997
|
this.rejectSubagent(sid, 'modified');
|
|
779
998
|
const s = this.mutationTarget(sid, 'modified');
|
|
@@ -930,11 +1149,13 @@ export class SessionService {
|
|
|
930
1149
|
return {};
|
|
931
1150
|
} })();
|
|
932
1151
|
let eventProfile = null;
|
|
1152
|
+
let hadSidecar = true;
|
|
933
1153
|
try {
|
|
934
1154
|
eventProfile = this.store.get(sid, { metadataOnly: true }).profile;
|
|
935
1155
|
}
|
|
936
1156
|
catch {
|
|
937
1157
|
eventProfile = null;
|
|
1158
|
+
hadSidecar = false;
|
|
938
1159
|
}
|
|
939
1160
|
// Python `_is_messaging_session_id`: decided before the JSON is gone, from WebUI metadata or the Agent's row.
|
|
940
1161
|
const isMessaging = (() => { try {
|
|
@@ -993,6 +1214,9 @@ export class SessionService {
|
|
|
993
1214
|
}
|
|
994
1215
|
}
|
|
995
1216
|
this.publish('session_delete', eventProfile);
|
|
1217
|
+
// Only a sidecar session is ever published to the relay.
|
|
1218
|
+
if (hadSidecar)
|
|
1219
|
+
this.deps.clearRelayCompletions?.(sid, eventProfile);
|
|
996
1220
|
return { ok: true, state_db_cleanup_failed: stateDbCleanupFailed, ...retained };
|
|
997
1221
|
}
|
|
998
1222
|
cleanup(zeroOnly) {
|
|
@@ -1316,6 +1540,10 @@ function findLastUserIndex(history) {
|
|
|
1316
1540
|
return i;
|
|
1317
1541
|
return null;
|
|
1318
1542
|
}
|
|
1543
|
+
/** Python `_sanitize_error`: absolute paths in an error message never reach the client. */
|
|
1544
|
+
export function sanitizePaths(error) {
|
|
1545
|
+
return str(error?.message ?? error).replace(/(?:(?:\/[a-zA-Z0-9_.-]+)+|(?:[A-Z]:\\[^\s]+))/g, '<path>');
|
|
1546
|
+
}
|
|
1319
1547
|
function truncationWatermarkFor(messages) {
|
|
1320
1548
|
if (!messages.length)
|
|
1321
1549
|
return 0.0;
|