@maudecode/talaria-web 1.14.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 +1913 -299
- package/dist/api/sessions-router.js +35 -24
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +35 -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 +2715 -418
- 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 +54 -16
- package/dist/runtime.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/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 +12 -1
- package/dist/sessions/merge.js +64 -7
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/relay.d.ts +7 -1
- package/dist/sessions/relay.js +17 -4
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +61 -3
- package/dist/sessions/service.js +243 -15
- 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 +304 -61
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +4 -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/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-emii55o0.js → Markdown-dy8t4dy7.js} +7 -7
- package/static/dist/assets/{RightPanel-o73y98dn.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-lnn2wo2s.js → SettingsLayout-n5d4wq4m.js} +1 -1
- package/static/dist/assets/{States-e5hv6gdh.js → States-kxyu50at.js} +1 -1
- package/static/dist/assets/{Toaster-c4jq1i26.js → Toaster-6zmbcqgg.js} +1 -1
- 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-m6nx9g8t.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-kejgxpil.js → abnfDiagram-VCTEODGH-cjqletra.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-hqnf84fc.js → architectureDiagram-5GKGNRK7-k5a7v36n.js} +1 -1
- package/static/dist/assets/back-e4m6nsnm.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-b195z021.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-olu212k1.js} +1 -1
- package/static/dist/assets/channel-gozy3dp3.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-b8o1nufs.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-l1seddto.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lo5zg15v.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-dsdzrp16.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-f3fxqq8b.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-h8egxtf6.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-iolwd8s0.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-b88bbtwi.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-e3e7nr6s.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-mdc46rt1.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-kqlllrrm.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-he585k2x.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gh94y9a4.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-euxp9y7e.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.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-gpchexqu.js → copied-kq0u3ht7.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lxd44ux4.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-njpiwat8.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-nvgy6yju.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-g98nyv72.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-ovgm6lvv.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-pb2bn2e4.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-6j4mvfqo.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-dqmb982r.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-ozbpzb6d.js} +1 -1
- package/static/dist/assets/{endpoints-e58saih3.js → endpoints-k8agnro8.js} +8 -8
- package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.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-geqx7zgl.js → ganttDiagram-EL5Y4UJY-ompixqmh.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-b6vxwp9q.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-k98f57on.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-cd3ff6ge.js} +1 -1
- package/static/dist/assets/index-nfy0ezs6.css +2 -0
- package/static/dist/assets/{index-f9bzglkn.js → index-nqed3oum.js} +3 -3
- package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-hjcwjami.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-oo7kdyte.js → infoDiagram-27XIBGKW-fm06rj36.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-bxak4mwu.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-khmihclr.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h1r32wdx.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-b1h2ykmb.js → lazyRouteComponent-b2eg0fws.js} +1 -1
- package/static/dist/assets/{line-laaa8h99.js → line-friyjof3.js} +1 -1
- package/static/dist/assets/{link-d8f0788e.js → link-m90ahx3n.js} +1 -1
- 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-ix4junux.js → mermaid-parser.core-f1gvpwrt.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-mt2oxwue.js} +1 -1
- package/static/dist/assets/{onboarding-lx0d6b38.js → onboarding-ret37j1e.js} +1 -1
- package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-jik4ixlf.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-erxlv6cc.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-mnkoldfl.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-i7lwrada.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-fr8ejdoy.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-otarjte2.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.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-jgroj2ew.js → railroadDiagram-O6MQD6OU-esyn75pw.js} +1 -1
- package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-k66keho6.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-cyhi5u29.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-jljpbo0h.js} +1 -1
- package/static/dist/assets/{saved-brdfjjnw.js → saved-do49si3v.js} +1 -1
- package/static/dist/assets/{search-gt2ueyvg.js → search-j5ki6a9o.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-fz0dhlu7.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.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-gcgscv8r.js → sliders-horizontal-c4wya4dy.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-kdnithdx.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.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-olvknr62.js → timeline-definition-24CTP7MA-g0qygccv.js} +1 -1
- package/static/dist/assets/todoStore-kjk5l0sy.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-ndchzkuu.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-hwjxj0zz.js} +1 -1
- package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-mbr6tuhu.js} +1 -1
- package/static/dist/assets/{useForm-kd759sjk.js → useForm-ocxu460f.js} +1 -1
- package/static/dist/assets/useLocale-fzy5jhg6.js +1 -0
- package/static/dist/assets/{useParams-katwvzil.js → useParams-lo9u33d1.js} +1 -1
- package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-lq1mr090.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-gubovd1r.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-ir3om65j.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-ce0or1ie.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.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-haqi9qzu.js +0 -1
- package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
- package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
- package/static/dist/assets/Select-jld3yeni.js +0 -1
- package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
- package/static/dist/assets/_app-bnip2pjg.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
- package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
- package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
- package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
- package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
- package/static/dist/assets/_app.memory-i00paisv.js +0 -1
- package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
- package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
- package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
- package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
- package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
- package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
- package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
- package/static/dist/assets/channel-o77ioktg.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
- package/static/dist/assets/index-gkh7kum4.css +0 -2
- package/static/dist/assets/login-llkohrcg.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
- package/static/dist/assets/share._token-oyjv046r.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
- package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
- package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
- package/static/dist/assets/useLocale-hds4xmja.js +0 -1
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* detail payload, list and search, and every mutation with its guards.
|
|
4
4
|
* Runtime concerns owned by other domains arrive through `SessionServiceDeps`.
|
|
5
5
|
*/
|
|
6
|
+
import type { PendingSteer } from '@maudecode/talaria-web-contracts';
|
|
6
7
|
import type { RunJournal } from './journal.js';
|
|
7
8
|
import type { DraftStore } from './drafts.js';
|
|
8
9
|
import type { SessionEventBus } from './events.js';
|
|
9
10
|
import { type ListParams, type ListResponse, type Row, type RuntimeOverlay } from './list.js';
|
|
10
11
|
import { Session, titleFrom, type Message } from './session.js';
|
|
11
12
|
import { type SessionStore } from './store.js';
|
|
13
|
+
import { type CompressionJob } from './compress.js';
|
|
14
|
+
import { type SidecarLike } from '../sidecar/client.js';
|
|
15
|
+
import { type Receipt } from './background-tasks.js';
|
|
12
16
|
import type { WorkspaceRegistry } from '../workspace/workspaces.js';
|
|
13
17
|
import { type ShareStore } from './shares.js';
|
|
14
18
|
import type { ProjectStore } from '../projects.js';
|
|
@@ -17,7 +21,20 @@ export declare class HttpFailure extends Error {
|
|
|
17
21
|
readonly extra: Record<string, unknown>;
|
|
18
22
|
constructor(status: number, message: string, extra?: Record<string, unknown>);
|
|
19
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Python `_agent_runtime_barrier_response`: a stale local Agent checkout is refused with a typed 409 before the caller
|
|
26
|
+
* mutates anything. Any other failure of the check is not a verdict and lets the caller proceed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureAgentRuntimeCurrent(sidecar: SidecarLike | null): Promise<void>;
|
|
20
29
|
export interface SessionServiceDeps {
|
|
30
|
+
/** TAL-255: the Agent sidecar for manual compression (`chat.compress`); null while it is down. */
|
|
31
|
+
sidecar?: () => SidecarLike | null;
|
|
32
|
+
/** Detached sidecar work for a profile: deletion waits for the returned release, so the home outlives the work. */
|
|
33
|
+
profileActivity?: (profile: string | null) => () => void;
|
|
34
|
+
/** A profile whose deletion has started takes no new detached work. */
|
|
35
|
+
profileDeleting?: (profile: string | null) => boolean;
|
|
36
|
+
/** TAL-372: the session's background work receipts, for the delegation rows that started them. */
|
|
37
|
+
backgroundReceipts?: (sid: string) => Receipt[];
|
|
21
38
|
store: SessionStore;
|
|
22
39
|
drafts: DraftStore;
|
|
23
40
|
events: SessionEventBus;
|
|
@@ -29,6 +46,8 @@ export interface SessionServiceDeps {
|
|
|
29
46
|
activeProfile: () => string;
|
|
30
47
|
isolatedProfileMode: () => boolean;
|
|
31
48
|
profilesMatch: (a: string | null | undefined, b: string | null | undefined) => boolean;
|
|
49
|
+
/** The agent's display name for a session's profile (`assistant_name`). */
|
|
50
|
+
assistantName: (profile: string | null) => string;
|
|
32
51
|
redactEnabled: () => boolean;
|
|
33
52
|
pinnedSessionsLimit: () => number;
|
|
34
53
|
runtime: RuntimeOverlay & {
|
|
@@ -36,12 +55,16 @@ export interface SessionServiceDeps {
|
|
|
36
55
|
activeRunStream: (sid: string) => string | null;
|
|
37
56
|
/** A live run whose journal missed a frame, so a replay cannot restore its whole output. */
|
|
38
57
|
journalDegraded: (streamId: string) => boolean;
|
|
58
|
+
/** TAL-424: the live stream's pending steers, as clients show them. */
|
|
59
|
+
pendingSteers?: (streamId: string) => PendingSteer[];
|
|
39
60
|
evictAgent: (sid: string) => void;
|
|
40
61
|
closeTerminal: (sid: string) => void;
|
|
41
62
|
/** Python `delete_cli_session`: remove the session's rows from the profile's state.db; resolves false on failure. */
|
|
42
63
|
deleteCliSession: (profile: string | null, sid: string) => Promise<boolean>;
|
|
43
64
|
};
|
|
44
65
|
attachmentDir: (sid: string) => string;
|
|
66
|
+
/** A deleted session can never be viewed, so its finished runs are cleared from the relay. */
|
|
67
|
+
clearRelayCompletions?: (sid: string, profile: string | null) => void;
|
|
45
68
|
/** Run journals are removed with their session (Python `delete_run_journal`). */
|
|
46
69
|
journal?: RunJournal;
|
|
47
70
|
hermesHome: string;
|
|
@@ -59,15 +82,21 @@ export interface SessionServiceDeps {
|
|
|
59
82
|
set: (sid: string, enabled: boolean) => void;
|
|
60
83
|
};
|
|
61
84
|
/** state.db sidebar rows for a profile (Python `get_cli_sessions`); null when the projection is unavailable. */
|
|
85
|
+
/** `truncated`: source kinds whose rows stopped at the per-kind window on this read (TAL-482). */
|
|
62
86
|
cliSessions: (profile: string, opts: {
|
|
63
87
|
sourceFilter: string | null;
|
|
64
|
-
}) =>
|
|
88
|
+
}) => {
|
|
89
|
+
rows: Row[];
|
|
90
|
+
truncated: ReadonlySet<string>;
|
|
91
|
+
};
|
|
65
92
|
profileHome: (profile: string) => string;
|
|
66
93
|
/** Python `commit_session_memory` (fire-and-forget): the cached Agent flushes memory for a session the user left. */
|
|
67
94
|
commitSessionMemory?: (sid: string) => void;
|
|
68
95
|
}
|
|
69
96
|
export declare class SessionService {
|
|
70
97
|
readonly deps: SessionServiceDeps;
|
|
98
|
+
/** TAL-255: one manual compression worker per session, shared by every compress route. */
|
|
99
|
+
private readonly compressionJobs;
|
|
71
100
|
constructor(deps: SessionServiceDeps);
|
|
72
101
|
private get store();
|
|
73
102
|
publish(reason: string, profile?: string | null, sessionId?: string | null): void;
|
|
@@ -96,7 +125,13 @@ export declare class SessionService {
|
|
|
96
125
|
* rows (a WebUI conversation continued from the CLI shows the CLI turns). This is the coordinate space `GET
|
|
97
126
|
* /api/session` exposes, so branching and the next model history slice/extend the same list.
|
|
98
127
|
*/
|
|
99
|
-
mergedTranscript(s: Session, local?: Message[]): Message[];
|
|
128
|
+
mergedTranscript(s: Session, local?: Message[], stateRows?: Message[]): Message[];
|
|
129
|
+
/**
|
|
130
|
+
* Python `reconciled_state_db_messages_for_session(prefer_context=True)`: the model history is the owner context
|
|
131
|
+
* extended append-only with the Agent's state.db rows (a CLI continuation of this session reaches the model), except
|
|
132
|
+
* for a compressed context whose anchor cannot be verified — that stays context-only.
|
|
133
|
+
*/
|
|
134
|
+
modelContext(s: Session, stateRows?: Message[]): Message[];
|
|
100
135
|
/** Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile. */
|
|
101
136
|
private lookupCliMeta;
|
|
102
137
|
/** Python `_session_index_marks_was_webui`: an index row that once owned a WebUI sidecar (self-heal 404). */
|
|
@@ -112,10 +147,14 @@ export declare class SessionService {
|
|
|
112
147
|
};
|
|
113
148
|
private mutationTarget;
|
|
114
149
|
private rejectSubagent;
|
|
150
|
+
/** TAL-372: each delegation row of a turn's scene carries the status of the work it started. */
|
|
151
|
+
backgroundLinked(s: Session, messages: unknown[]): unknown[];
|
|
115
152
|
/** `compact()` plus messages, redacted for the wire (Python `_public_session_projection`). */
|
|
116
153
|
publicSession(s: Session, withMessages?: boolean): Record<string, unknown>;
|
|
117
154
|
/** `compact()` with the wire streaming/read-only flags (TAL-312), for replies that return the session row. */
|
|
118
155
|
wireRow(s: Session): Record<string, unknown>;
|
|
156
|
+
/** The agent's display name for a session's profile (`assistant_name`). */
|
|
157
|
+
assistantName(s: Session): string;
|
|
119
158
|
/** Python `public_session_projection(s.__dict__)`: every persisted field, redacted (session export). */
|
|
120
159
|
publicSessionDocument(s: Session): Record<string, unknown>;
|
|
121
160
|
private loadRevision;
|
|
@@ -139,7 +178,7 @@ export declare class SessionService {
|
|
|
139
178
|
private foreignSessionDetail;
|
|
140
179
|
/** Clear persisted streaming flags when no live stream backs them (Python `_clear_stale_stream_state`, no journal recovery). */
|
|
141
180
|
clearStaleStreamState(session: Session): boolean;
|
|
142
|
-
list(params: Omit<ListParams, 'activeProfile' | 'isolatedProfileMode' | 'profilesMatch' | 'cliRows' | 'gatewayIdentity' | 'stateDbSources'>): {
|
|
181
|
+
list(params: Omit<ListParams, 'activeProfile' | 'isolatedProfileMode' | 'profilesMatch' | 'cliRows' | 'gatewayIdentity' | 'stateDbSources' | 'truncatedSources'>): {
|
|
143
182
|
body: ListResponse;
|
|
144
183
|
etag: string;
|
|
145
184
|
};
|
|
@@ -173,6 +212,23 @@ export declare class SessionService {
|
|
|
173
212
|
/** Python `/api/personality/set` persistence: the name only; the prompt is resolved by the caller. */
|
|
174
213
|
setPersonality(sid: string, name: string | null): Promise<string | null>;
|
|
175
214
|
truncate(sid: string, keepRaw: unknown): Promise<Record<string, unknown>>;
|
|
215
|
+
/**
|
|
216
|
+
* Python `_handle_session_compress_start`: validate, then join the session's running job or start a fresh one (a
|
|
217
|
+
* finished job is replaced). The stale-runtime refusal comes before a job exists; a running job is joined without it.
|
|
218
|
+
*/
|
|
219
|
+
startCompression(sid: string, focusRaw: unknown): Promise<CompressionJob>;
|
|
220
|
+
/** The session a compression may run on now, and the model history it compresses: the context a turn would send, so a
|
|
221
|
+
* repeat compression builds on the previous summary instead of re-reading the whole display transcript. */
|
|
222
|
+
private compressionTarget;
|
|
223
|
+
/** The session's compression job; a deleted session's finished result is dropped rather than served. */
|
|
224
|
+
compressionJob(sid: string): CompressionJob | undefined;
|
|
225
|
+
/**
|
|
226
|
+
* Python `_handle_session_compress`: compress the sanitized transcript in the sidecar outside the lock, then under the
|
|
227
|
+
* session lock refuse a result the session moved past (stream state or transcript changed) and install it as the
|
|
228
|
+
* model context with the manual anchor and the #4836 boundary that keeps state.db from replaying compressed rows.
|
|
229
|
+
* The guards run again first: a stream may have started while the caller awaited the runtime check.
|
|
230
|
+
*/
|
|
231
|
+
private compressSession;
|
|
176
232
|
clear(sid: string): Promise<Record<string, unknown>>;
|
|
177
233
|
retry(sid: string): Promise<Record<string, unknown>>;
|
|
178
234
|
undo(sid: string): Promise<Record<string, unknown>>;
|
|
@@ -195,6 +251,8 @@ export declare const AUTO_TITLE_LABELS: Set<string>;
|
|
|
195
251
|
/** Python `apply_session_title_rename`. */
|
|
196
252
|
export declare function applySessionTitleRename(session: Session, rawTitle: unknown): string;
|
|
197
253
|
export declare function markSessionTitleGenerated(session: Session): void;
|
|
254
|
+
/** Python `_sanitize_error`: absolute paths in an error message never reach the client. */
|
|
255
|
+
export declare function sanitizePaths(error: unknown): string;
|
|
198
256
|
/** Align model context with the display prefix (Python `truncate_context_for_display_keep`, simplified to the row-count contract). */
|
|
199
257
|
export declare function truncateContextForDisplayKeep(context: Message[] | null | undefined, full: unknown[], keep: number): Message[];
|
|
200
258
|
export declare function truncateSessionAtKeep(session: Session, keep: number): [number, number];
|
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(withAttachmentObjects(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,7 +362,7 @@ 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,
|
|
@@ -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;
|