@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/turn.js
CHANGED
|
@@ -14,7 +14,7 @@ import { HttpFailure, markSessionTitleGenerated } from './service.js';
|
|
|
14
14
|
import { StreamRegistry, SessionChannels } from './streams.js';
|
|
15
15
|
import { PendingPrompts, clarifyReply } from './pending.js';
|
|
16
16
|
import { RunJournal } from './journal.js';
|
|
17
|
-
import { Session, titleFrom } from './session.js';
|
|
17
|
+
import { CONTEXT_USAGE_FIELDS, Session, titleFrom } from './session.js';
|
|
18
18
|
import { buildActiveTurnToken, completedToolIndex, publicToolFrame, redactSessionData, redactString, withToolId } from '../redact.js';
|
|
19
19
|
import { dict } from '../config/agent-config.js';
|
|
20
20
|
import { ReasoningTitleTracker, reasoningEventPayload } from './reasoning-titles.js';
|
|
@@ -24,12 +24,15 @@ import { withSessionWireFlags } from './list.js';
|
|
|
24
24
|
import { hydrateAnchorActivityScenes, turnTerminalState, withTurnIds } from './anchor.js';
|
|
25
25
|
import { persistentStateChanges, persistentStateSnapshot } from './state-saved.js';
|
|
26
26
|
import { maxIterationsFromConfig, maxTokensFromConfig, processWakeupMaxIterations, reasoningConfigFromConfig, webuiEphemeralSystemPrompt, workspaceSystemMessage } from './turn-context.js';
|
|
27
|
-
import { agentSteerText, assistantReplyAddedAfterCurrentTurn, buildPartialMessage, checkpointTurnStart, extractToolCallsFromMessages, injectMaxIterationSummaryFallback,
|
|
28
|
-
import {
|
|
27
|
+
import { agentSteerText, assistantReplyAddedAfterCurrentTurn, buildPartialMessage, escapeWorkspacePrefixPath, checkpointTurnStart, extractToolCallsFromMessages, injectMaxIterationSummaryFallback, isDict, mergeDisplayMessagesAfterAgentResult, messageIdentity, messageText, pendingUserRow, sanitizeMessagesForApi, sessionLacksFinalAssistantAnswer, splitThinkingFromContent, stoppedTurnContext, stripXmlToolCalls, toolOutcome, withAttachmentObjects, withBodyExcerpts, withToolCallOutcomes, workspaceContextPrefix } from './merge.js';
|
|
28
|
+
import { mayBecomeSilentReply, turnOrigin, withBackgroundUpdates } from './background-updates.js';
|
|
29
|
+
import { fallbackTitleFromExchange, firstExchangeSnippets, isGenericFallbackTitle, latestExchangeSnippets, looksInvalidGeneratedTitle, looksLikeDefaultCliTitle, sanitizeGeneratedTitle, titleLanguageMismatch, titlePrompts } from './titles.js';
|
|
29
30
|
import { str } from '../util.js';
|
|
30
31
|
export const CHAT_LOCK_WAIT_SECONDS = 2;
|
|
31
32
|
const IMAGE_MODE_TIMEOUT_MS = 15_000;
|
|
32
33
|
const TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT = 80;
|
|
34
|
+
/** How long a user's message waits for a stopped background turn to unwind before its own turn is admitted. */
|
|
35
|
+
const BACKGROUND_UNWIND_WAIT_MS = 30_000;
|
|
33
36
|
export function classifyProviderError(errStr, opts = {}) {
|
|
34
37
|
const lower = errStr.toLowerCase();
|
|
35
38
|
if (opts.condition === 'credential_missing')
|
|
@@ -148,6 +151,11 @@ export class TurnRunner {
|
|
|
148
151
|
abortControllers = new Map();
|
|
149
152
|
steers = new Map();
|
|
150
153
|
consumedSteers = new Map();
|
|
154
|
+
/** Per stream: steer, withdraw and Send now run one at a time, so each sees the queue the Agent holds. */
|
|
155
|
+
steerChains = new Map();
|
|
156
|
+
/** Streams whose Agent steer queue a withdraw or Send now is rewriting right now, and the pending text seen meanwhile. */
|
|
157
|
+
steerRewrites = new Map();
|
|
158
|
+
deferredPendingSteer = new Map();
|
|
151
159
|
lastCompletedTool = new Map();
|
|
152
160
|
/** Streams whose run completed (`done` emitted) and only await title work; a late cancel is a no-op for these. */
|
|
153
161
|
settledStreams = new Set();
|
|
@@ -158,6 +166,8 @@ export class TurnRunner {
|
|
|
158
166
|
/** TAL-364: a Stop's in-flight `chat.interrupt` reply (null when it failed), so a worker that settles first can use its checkpoint. */
|
|
159
167
|
interrupts = new Map();
|
|
160
168
|
stopContexts = new Map();
|
|
169
|
+
/** Each turn's worker, so a stopped background turn can be awaited before the user's turn takes the session. */
|
|
170
|
+
workers = new Map();
|
|
161
171
|
constructor(deps) {
|
|
162
172
|
this.deps = deps;
|
|
163
173
|
}
|
|
@@ -184,9 +194,10 @@ export class TurnRunner {
|
|
|
184
194
|
return { error: 'session already has an active stream', active_stream_id: blocking, _status: 409 };
|
|
185
195
|
}
|
|
186
196
|
const streamId = randomUUID().replace(/-/g, '');
|
|
187
|
-
const wasHiddenEmpty = !s.messages.length && !s.context_messages.length && !s.
|
|
197
|
+
const wasHiddenEmpty = !s.messages.length && !s.context_messages.length && !s.hasPendingPrompt;
|
|
188
198
|
const attachments = opts.attachments ?? [];
|
|
189
|
-
|
|
199
|
+
// A background wakeup stays one in a forked session too (TAL-460).
|
|
200
|
+
const source = opts.source !== 'process_wakeup' && str(s.source_tag).toLowerCase() === 'fork' ? 'fork' : (opts.source ?? 'webui');
|
|
190
201
|
s.workspace = opts.workspace;
|
|
191
202
|
s.model = opts.model;
|
|
192
203
|
s.model_provider = opts.modelProvider;
|
|
@@ -205,8 +216,8 @@ export class TurnRunner {
|
|
|
205
216
|
this.checkpointUserMessage(s, opts.msg, attachments, s.pending_started_at, source, streamId);
|
|
206
217
|
this.deps.store.save(s);
|
|
207
218
|
this.registry.writebackOwners.set(s.session_id, streamId);
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
// A new chat appears in the list; every turn tells open clients a run started (TAL-434).
|
|
220
|
+
this.deps.events.publish(wasHiddenEmpty ? 'session_new' : 'turn_started', { profile: s.profile, sessionId: s.session_id });
|
|
210
221
|
try {
|
|
211
222
|
this.deps.workspaces.setLastWorkspace(opts.workspace, s.profile);
|
|
212
223
|
}
|
|
@@ -214,10 +225,11 @@ export class TurnRunner {
|
|
|
214
225
|
const channel = this.registry.create(streamId, s.session_id);
|
|
215
226
|
if (opts.goalRelated)
|
|
216
227
|
this.registry.goalRelated.add(streamId);
|
|
217
|
-
this.registry.registerActiveRun({ stream_id: streamId, session_id: s.session_id, started_at: this.deps.now(), phase: 'starting', workspace: opts.workspace, model: opts.model, provider: opts.modelProvider, ephemeral: Boolean(opts.ephemeral) });
|
|
218
|
-
|
|
228
|
+
this.registry.registerActiveRun({ stream_id: streamId, session_id: s.session_id, started_at: this.deps.now(), phase: 'starting', workspace: opts.workspace, model: opts.model, provider: opts.modelProvider, ephemeral: Boolean(opts.ephemeral), origin: turnOrigin(source) });
|
|
229
|
+
const worker = this.run(s.session_id, streamId, channel, opts).catch((error) => {
|
|
219
230
|
this.deps.log(`[webui] ERROR turn ${streamId} crashed\n${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
220
|
-
});
|
|
231
|
+
}).finally(() => { this.workers.delete(streamId); });
|
|
232
|
+
this.workers.set(streamId, worker);
|
|
221
233
|
const response = { stream_id: streamId, session_id: s.session_id, pending_started_at: s.pending_started_at, turn_id: streamId, title: s.title };
|
|
222
234
|
if (opts.normalizedModel && opts.model)
|
|
223
235
|
response.effective_model = opts.model;
|
|
@@ -246,7 +258,7 @@ export class TurnRunner {
|
|
|
246
258
|
const put = (event, data, meta = {}) => {
|
|
247
259
|
for (const steerEvent of this.takeSteerEventsBefore(streamId, event))
|
|
248
260
|
put(steerEvent[0], steerEvent[1]);
|
|
249
|
-
if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', '
|
|
261
|
+
if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', 'steer_withdrawn'].includes(event))
|
|
250
262
|
return;
|
|
251
263
|
// `cancel()` already wrote the terminal row and closed the stream: the worker's unwind adds no second one.
|
|
252
264
|
if (event === 'cancel' && !this.registry.streams.has(streamId)) {
|
|
@@ -290,11 +302,7 @@ export class TurnRunner {
|
|
|
290
302
|
}
|
|
291
303
|
const msgText = opts.msg;
|
|
292
304
|
const previousMessages = structuredClone(s.messages);
|
|
293
|
-
|
|
294
|
-
// extended append-only with the Agent's state.db rows (a CLI continuation of this session reaches the model), except
|
|
295
|
-
// for a compressed context whose anchor cannot be verified — that stays context-only.
|
|
296
|
-
const localContext = s.context_messages.length ? s.context_messages : s.messages.filter((m) => !m._error && !m._partial);
|
|
297
|
-
const previousContext = structuredClone(localContext.some((m) => isContextCompressionMarker(m)) ? localContext : deps.service().mergedTranscript(s, localContext));
|
|
305
|
+
const previousContext = structuredClone(deps.service().modelContext(s));
|
|
298
306
|
// Python `_sanitize_messages_for_api`: the model never sees display-only rows or a replayed cancelled prompt.
|
|
299
307
|
const apiHistory = sanitizeMessagesForApi(previousContext);
|
|
300
308
|
const workspaceCtx = workspaceContextPrefix(opts.workspace);
|
|
@@ -314,6 +322,11 @@ export class TurnRunner {
|
|
|
314
322
|
const toolStartedAt = new WeakMap();
|
|
315
323
|
let tokenSent = false;
|
|
316
324
|
let firstTokenAt = null;
|
|
325
|
+
// TAL-460: a background turn's text is held back while it could still be a silence marker, so one never flashes by.
|
|
326
|
+
// ponytail: checks the turn's whole text, so a marker after earlier prose streams until the settled row hides it;
|
|
327
|
+
// check the last text segment instead if that shows up.
|
|
328
|
+
const holdSilence = turnOrigin(opts.source) === 'background';
|
|
329
|
+
let heldText = '';
|
|
317
330
|
const titles = new ReasoningTitleTracker();
|
|
318
331
|
let capturedTerminalError = null;
|
|
319
332
|
const controller = new AbortController();
|
|
@@ -327,6 +340,8 @@ export class TurnRunner {
|
|
|
327
340
|
activeRun.phase = 'running';
|
|
328
341
|
const settledAt = { value: false };
|
|
329
342
|
let failed = false;
|
|
343
|
+
// TAL-424: steers an errored turn ended without taking, sent as one follow-up turn once this one is torn down.
|
|
344
|
+
let followUp = [];
|
|
330
345
|
try {
|
|
331
346
|
if (!sidecar)
|
|
332
347
|
throw new SidecarError('The Agent sidecar is not running; chat is unavailable until it starts.', { condition: 'sidecar_unavailable' });
|
|
@@ -349,7 +364,7 @@ export class TurnRunner {
|
|
|
349
364
|
const frozenWorkspace = str(s.created_workspace) || str(s.workspace);
|
|
350
365
|
const turnContext = {
|
|
351
366
|
system_message: workspaceSystemMessage(frozenWorkspace),
|
|
352
|
-
ephemeral_system_prompt: webuiEphemeralSystemPrompt({ config: cfg, personality: str(s.personality) || null, sessionId, profile: s.profile ?? null, workspace: frozenWorkspace, hermesHome: deps.profileHome(s.profile), homeDisplay: deps.homeDisplay?.() ?? '~/.hermes' }),
|
|
367
|
+
ephemeral_system_prompt: webuiEphemeralSystemPrompt({ config: cfg, personality: str(s.personality) || null, sessionId, profile: s.profile ?? null, workspace: frozenWorkspace, hermesHome: deps.profileHome(s.profile), homeDisplay: deps.homeDisplay?.() ?? '~/.hermes', background: turnOrigin(opts.source) === 'background' }),
|
|
353
368
|
max_iterations: processWakeupMaxIterations(maxIterationsFromConfig(cfg), opts.source ?? 'webui', deps.env ?? {}),
|
|
354
369
|
max_tokens: maxTokensFromConfig(cfg),
|
|
355
370
|
reasoning_config: reasoningConfigFromConfig(cfg, opts.modelProvider ?? s.model_provider ?? null),
|
|
@@ -375,7 +390,12 @@ export class TurnRunner {
|
|
|
375
390
|
if (stable !== null)
|
|
376
391
|
put('reasoning', { text: '', titles: stable });
|
|
377
392
|
partialText.push(str(data.text));
|
|
378
|
-
|
|
393
|
+
if (holdSilence && mayBecomeSilentReply(partialText.join(''))) {
|
|
394
|
+
heldText += str(data.text);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
put('token', { text: heldText + str(data.text) });
|
|
398
|
+
heldText = '';
|
|
379
399
|
return;
|
|
380
400
|
}
|
|
381
401
|
case 'reasoning': {
|
|
@@ -387,9 +407,12 @@ export class TurnRunner {
|
|
|
387
407
|
return;
|
|
388
408
|
}
|
|
389
409
|
case 'steer_pending':
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
410
|
+
// TAL-424: while a withdraw or Send now rewrites the Agent's queue its pending text is ambiguous; it settles after.
|
|
411
|
+
if (this.steerRewrites.has(streamId)) {
|
|
412
|
+
this.deferredPendingSteer.set(streamId, str(data.text));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.settleConsumedSteers(sessionId, streamId, str(data.text));
|
|
393
416
|
return;
|
|
394
417
|
case 'tool': {
|
|
395
418
|
const call = { name: data.name, args: data.args ?? {}, tid: str(data.tid), done: false };
|
|
@@ -494,7 +517,7 @@ export class TurnRunner {
|
|
|
494
517
|
// sidecar reports `completed` whenever a failed run still carries messages).
|
|
495
518
|
// Python's second chance: a turn that emitted no new row still counts when the merged transcript it produced
|
|
496
519
|
// ends on a final answer (the current user row or trailing tool activity makes it "lacking").
|
|
497
|
-
const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId });
|
|
520
|
+
const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId, attachments: opts.attachments ?? [] });
|
|
498
521
|
const assistantAdded = assistantReplyAddedAfterCurrentTurn(resultMessages, previousContext, msgText) || !sessionLacksFinalAssistantAnswer(mergedForCheck());
|
|
499
522
|
const lastErr = result.error ?? capturedTerminalError ?? '';
|
|
500
523
|
// Python `_turn_transcript_lacks_final_assistant_answer`: a partial result with no final answer is a silent failure even if tokens streamed.
|
|
@@ -503,8 +526,11 @@ export class TurnRunner {
|
|
|
503
526
|
const classification = classifyProviderError(lastErr, { silentFailure: !lastErr });
|
|
504
527
|
const errStr = lastErr || `${classification.label}.`;
|
|
505
528
|
const payload = providerErrorPayload(errStr, classification.type, classification.hint, deps.redactEnabled());
|
|
506
|
-
// Settle the steers first so the persisted turn carries every consumed one; the Agent's pending text
|
|
507
|
-
|
|
529
|
+
// Settle the steers first so the persisted turn carries every consumed one; the Agent's pending text is sent next.
|
|
530
|
+
// A withdraw or Send now still in flight finishes first, so the settlement sees the queue it left.
|
|
531
|
+
await this.steerRewrites.get(streamId);
|
|
532
|
+
const { events: steerEvents, leftovers } = this.finalizeSteers(streamId, str(result.pending_steer), 'followup');
|
|
533
|
+
followUp = leftovers;
|
|
508
534
|
this.persistError(s, streamId, classification.label, payload, activeTurnToken);
|
|
509
535
|
payload.session = redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled());
|
|
510
536
|
payload.session_id = s.session_id;
|
|
@@ -517,8 +543,9 @@ export class TurnRunner {
|
|
|
517
543
|
}
|
|
518
544
|
// ── settle the transcript ──
|
|
519
545
|
// The Agent's last pending-steer text settles the remaining steers before the turn is written back.
|
|
520
|
-
|
|
521
|
-
|
|
546
|
+
await this.steerRewrites.get(streamId);
|
|
547
|
+
const { events: steerEvents, leftovers } = this.finalizeSteers(streamId, result.pending_steer, 'followup');
|
|
548
|
+
s.messages = mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId, attachments: opts.attachments ?? [] });
|
|
522
549
|
s.context_messages = dedupeContext(resultMessages);
|
|
523
550
|
for (const m of s.messages) {
|
|
524
551
|
if (m.role !== 'assistant')
|
|
@@ -567,7 +594,8 @@ export class TurnRunner {
|
|
|
567
594
|
if (m.role === 'user') {
|
|
568
595
|
const content = messageText(m.content);
|
|
569
596
|
const base = msgText.includes('\n\n[Attached files:') ? (msgText.split('\n\n[Attached files:')[0] ?? '').trim() : msgText;
|
|
570
|
-
|
|
597
|
+
// An attachment-only prompt has no text to match: only this turn's own row takes its files.
|
|
598
|
+
if (msgText ? content.includes(base.slice(0, 60)) || msgText.includes(content.slice(0, 60)) : m._turn_id === streamId)
|
|
571
599
|
m.attachments = [...attachments];
|
|
572
600
|
break;
|
|
573
601
|
}
|
|
@@ -635,6 +663,10 @@ export class TurnRunner {
|
|
|
635
663
|
cache_hit_percent: cacheHit(cacheReadTokens, inputTokens), turn_cache_hit_percent: cacheHit(turnCacheRead, turnInput), duration_seconds: Math.round(duration * 1000) / 1000,
|
|
636
664
|
context_length: s.context_length ?? 0, threshold_tokens: s.threshold_tokens ?? 0, last_prompt_tokens: s.last_prompt_tokens ?? 0,
|
|
637
665
|
};
|
|
666
|
+
const doneSession = redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled());
|
|
667
|
+
// TAL-299: the ring's figures match the terminal session's (and a detail reload's).
|
|
668
|
+
for (const key of CONTEXT_USAGE_FIELDS)
|
|
669
|
+
doneUsage[key] = doneSession[key];
|
|
638
670
|
if (usage.completion_tokens && duration > 0)
|
|
639
671
|
doneUsage.tps = Math.round((usage.completion_tokens / duration) * 10) / 10;
|
|
640
672
|
if (firstTokenAt !== null)
|
|
@@ -643,7 +675,7 @@ export class TurnRunner {
|
|
|
643
675
|
if (usedModel)
|
|
644
676
|
doneUsage.used_model = usedModel;
|
|
645
677
|
const donePayload = {
|
|
646
|
-
session:
|
|
678
|
+
session: doneSession,
|
|
647
679
|
usage: doneUsage,
|
|
648
680
|
terminal_state: turnTerminalState(s.messages, streamId),
|
|
649
681
|
};
|
|
@@ -657,6 +689,7 @@ export class TurnRunner {
|
|
|
657
689
|
// worker before its daemon-thread title generation).
|
|
658
690
|
this.registry.activeRuns.delete(streamId);
|
|
659
691
|
this.settledStreams.add(streamId);
|
|
692
|
+
this.startSteerFollowUp(sessionId, leftovers);
|
|
660
693
|
await this.backgroundTitle(s, put);
|
|
661
694
|
// Python: the last non-error assistant reply with content, else "(no answer produced)".
|
|
662
695
|
if (opts.onDone) {
|
|
@@ -720,21 +753,26 @@ export class TurnRunner {
|
|
|
720
753
|
// not race the error writeback above (a re-saved session would resurface in the sidebar).
|
|
721
754
|
if (failed)
|
|
722
755
|
opts.onFailed?.();
|
|
756
|
+
this.startSteerFollowUp(sessionId, followUp);
|
|
723
757
|
}
|
|
724
758
|
}
|
|
725
759
|
/**
|
|
726
760
|
* Python `_build_user_message`: image attachments are embedded as native `image_url` parts only when the Agent's
|
|
727
761
|
* resolved image mode for this model is `native` (text mode routes them through the Agent's vision tool path), and
|
|
728
|
-
* only after the bytes are read through an anchored descriptor and sniffed as a real image format.
|
|
762
|
+
* only after the bytes are read through an anchored descriptor and sniffed as a real image format. Every attached
|
|
763
|
+
* file is also named by path after the text (TAL-276), so an attachment-only turn still gives the model a request
|
|
764
|
+
* and its transcript row a distinct identity.
|
|
729
765
|
*/
|
|
730
766
|
async buildUserMessage(workspaceCtx, msgText, attachments, workspace, sessionId, s, opts, signal) {
|
|
731
767
|
const text = workspaceCtx + msgText;
|
|
768
|
+
const named = attachments.map((att) => str(att.path).trim()).filter(Boolean).map(escapeWorkspacePrefixPath);
|
|
769
|
+
const withFiles = () => (named.length ? `${text}\n\n[Attached files: ${named.join(', ')}]` : text);
|
|
732
770
|
const candidates = attachments.filter((att) => str(att.path).trim() && str(att.mime).trim().startsWith('image/'));
|
|
733
771
|
if (!candidates.length)
|
|
734
|
-
return
|
|
772
|
+
return withFiles();
|
|
735
773
|
const sidecar = this.deps.sidecar();
|
|
736
774
|
if (!sidecar)
|
|
737
|
-
return
|
|
775
|
+
return withFiles();
|
|
738
776
|
// A cancel that landed before this point is final: never start the lookup or wait on it.
|
|
739
777
|
if (signal.aborted)
|
|
740
778
|
return text;
|
|
@@ -750,15 +788,15 @@ export class TurnRunner {
|
|
|
750
788
|
if (mode.mode !== 'native') {
|
|
751
789
|
const cfg = (await this.deps.profileConfig?.(s.profile ?? null)) ?? {};
|
|
752
790
|
if (explicitTextSignal(cfg) || mode.supports_vision === false)
|
|
753
|
-
return
|
|
791
|
+
return withFiles();
|
|
754
792
|
}
|
|
755
793
|
}
|
|
756
794
|
catch (error) {
|
|
757
795
|
if (!signal.aborted)
|
|
758
796
|
this.deps.log(`[webui] image mode lookup failed for ${sessionId}: ${error.message}`);
|
|
759
|
-
return
|
|
797
|
+
return withFiles();
|
|
760
798
|
}
|
|
761
|
-
const parts = [
|
|
799
|
+
const parts = [];
|
|
762
800
|
let images = 0;
|
|
763
801
|
const roots = [workspace, this.deps.attachmentDir(sessionId)].map((r) => resolvePathLikePython(r));
|
|
764
802
|
for (const att of candidates) {
|
|
@@ -790,15 +828,16 @@ export class TurnRunner {
|
|
|
790
828
|
closeSync(fd);
|
|
791
829
|
}
|
|
792
830
|
}
|
|
793
|
-
return images ? parts :
|
|
831
|
+
return images ? [{ type: 'text', text: withFiles() }, ...parts] : withFiles();
|
|
794
832
|
}
|
|
795
833
|
/**
|
|
796
834
|
* Python `_session_payload_with_terminal_window`: the same visible-message window contract as `GET /api/session?
|
|
797
835
|
* msg_limit=` (renderable rows, limited payload shape, windowed tool calls, todo state) with the full count.
|
|
798
836
|
*/
|
|
799
837
|
terminalSessionPayload(s) {
|
|
800
|
-
const payload = withSessionWireFlags(s.compact(), this.registry.liveIds);
|
|
801
|
-
|
|
838
|
+
const payload = withSessionWireFlags(s.compact({ contextLengthFor: this.deps.service().deps.contextLengthFor }), this.registry.liveIds);
|
|
839
|
+
payload.assistant_name = this.deps.service().assistantName(s);
|
|
840
|
+
const scened = withBodyExcerpts(this.deps.service().backgroundLinked(s, hydrateAnchorActivityScenes(withToolCallOutcomes(withBackgroundUpdates(withTurnIds(withAttachmentObjects(s.messages)), s), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: true })), s.active_stream_id);
|
|
802
841
|
const [window, offset] = messageWindowForDisplay(scened, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
|
|
803
842
|
const limited = messagesForLimitedPayload(window);
|
|
804
843
|
payload.messages = limited;
|
|
@@ -853,7 +892,7 @@ export class TurnRunner {
|
|
|
853
892
|
}
|
|
854
893
|
materializePendingUserTurn(s, activeTurnToken, turnId) {
|
|
855
894
|
const pendingText = str(s.pending_user_message);
|
|
856
|
-
if (!
|
|
895
|
+
if (!s.hasPendingPrompt)
|
|
857
896
|
return false;
|
|
858
897
|
const recoveredTs = typeof s.pending_started_at === 'number' && s.pending_started_at > 0 ? s.pending_started_at : this.deps.now();
|
|
859
898
|
const source = s.pending_user_source ?? 'webui';
|
|
@@ -932,6 +971,11 @@ export class TurnRunner {
|
|
|
932
971
|
this.deps.store.sessions.delete(current.session_id);
|
|
933
972
|
return true;
|
|
934
973
|
}
|
|
974
|
+
// TAL-460: a background turn stops quietly: its prompt and model context are kept, but no partial and no Stop row,
|
|
975
|
+
// and its first settlement is final.
|
|
976
|
+
const quiet = this.registry.activeRuns.get(streamId)?.origin === 'background';
|
|
977
|
+
if (quiet && current.active_stream_id === null)
|
|
978
|
+
return true;
|
|
935
979
|
const stop = this.stopContexts.get(streamId);
|
|
936
980
|
const canonical = stop !== undefined && checkpoint !== null && checkpointTurnStart(checkpoint, stop.msgText, stop.historyLength) !== null;
|
|
937
981
|
const settle = () => {
|
|
@@ -967,8 +1011,10 @@ export class TurnRunner {
|
|
|
967
1011
|
current.pending_attachments = [];
|
|
968
1012
|
current.pending_started_at = null;
|
|
969
1013
|
current.pending_user_source = null;
|
|
970
|
-
|
|
971
|
-
|
|
1014
|
+
if (!quiet) {
|
|
1015
|
+
this.appendPartialSnapshot(current, streamId);
|
|
1016
|
+
current.messages.push({ role: 'assistant', content: '', _error: true, _terminal_state: 'cancelled', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
|
|
1017
|
+
}
|
|
972
1018
|
this.persistConsumedSteers(current, streamId, startedAt, this.deps.now());
|
|
973
1019
|
try {
|
|
974
1020
|
this.deps.store.save(current);
|
|
@@ -1077,7 +1123,9 @@ export class TurnRunner {
|
|
|
1077
1123
|
const sessionId = s.session_id;
|
|
1078
1124
|
const placeholder = str(s.title).trim();
|
|
1079
1125
|
const invalidExisting = looksInvalidGeneratedTitle(s.title);
|
|
1080
|
-
|
|
1126
|
+
// A claimed CLI session still carrying its default `<Source> Session` title is retitled like a new chat (TAL-256).
|
|
1127
|
+
const defaultTitle = (session) => looksLikeDefaultCliTitle({ title: session.title, source_tag: session.source_tag, raw_source: session.raw_source, session_source: session.session_source, source_label: session.source_label });
|
|
1128
|
+
const eligible = ['Untitled', 'New Chat', ''].includes(placeholder) || placeholder === titleFrom(s.messages, '') || invalidExisting || defaultTitle(s);
|
|
1081
1129
|
const [userText, assistantText] = firstExchangeSnippets(s.messages);
|
|
1082
1130
|
// Python `_put_title_status`: empty reason/title/raw_preview keys are omitted.
|
|
1083
1131
|
const status = (status, reason = '', title = '', rawPreview = '') => {
|
|
@@ -1116,7 +1164,7 @@ export class TurnRunner {
|
|
|
1116
1164
|
let effective = str(current.title).trim();
|
|
1117
1165
|
let wrote = false;
|
|
1118
1166
|
if (next) {
|
|
1119
|
-
const stillAuto = effective === placeholder || ['Untitled', 'New Chat', ''].includes(effective) || effective === titleFrom(current.messages, '') || looksInvalidGeneratedTitle(current.title);
|
|
1167
|
+
const stillAuto = effective === placeholder || ['Untitled', 'New Chat', ''].includes(effective) || effective === titleFrom(current.messages, '') || looksInvalidGeneratedTitle(current.title) || defaultTitle(current);
|
|
1120
1168
|
if (current.manual_title || !stillAuto) {
|
|
1121
1169
|
status('skipped', 'manual_title', effective);
|
|
1122
1170
|
return;
|
|
@@ -1144,16 +1192,19 @@ export class TurnRunner {
|
|
|
1144
1192
|
}
|
|
1145
1193
|
}
|
|
1146
1194
|
// ── cancel / steer ───────────────────────────────────────────────────────
|
|
1147
|
-
/**
|
|
1195
|
+
/**
|
|
1196
|
+
* Python `cancel_stream`: persist the partial, mark cancelled, interrupt the Agent, release admission. `withdrawn` is the
|
|
1197
|
+
* `steer_withdrawn` (stopped) this Stop emitted, for the cancel response (TAL-426).
|
|
1198
|
+
*/
|
|
1148
1199
|
async cancel(streamId) {
|
|
1149
1200
|
const channel = this.registry.peek(streamId);
|
|
1150
1201
|
const run = this.registry.activeRuns.get(streamId);
|
|
1151
1202
|
if (!channel && !run)
|
|
1152
|
-
return false;
|
|
1203
|
+
return { cancelled: false, withdrawn: [] };
|
|
1153
1204
|
// Python popped the run before its title work: a cancel that lands after `done` (while the title prompts still run)
|
|
1154
1205
|
// is not a cancellation, so it neither journals a `cancel` frame nor marks the completed run interrupted.
|
|
1155
1206
|
if (!run && this.settledStreams.has(streamId))
|
|
1156
|
-
return false;
|
|
1207
|
+
return { cancelled: false, withdrawn: [] };
|
|
1157
1208
|
const sessionId = this.registry.ownerSessionId(streamId) ?? run?.session_id ?? null;
|
|
1158
1209
|
this.registry.cancelled.add(streamId);
|
|
1159
1210
|
if (run) {
|
|
@@ -1174,7 +1225,9 @@ export class TurnRunner {
|
|
|
1174
1225
|
const leftover = str(reply?.pending_steer);
|
|
1175
1226
|
const checkpoint = reply?.checkpoint ?? null;
|
|
1176
1227
|
// Settle the steers first so the persisted cancel carries every consumed one.
|
|
1177
|
-
|
|
1228
|
+
// TAL-424: a Stop withdraws the steers the Agent never took, with their text, and starts nothing after.
|
|
1229
|
+
await this.steerRewrites.get(streamId);
|
|
1230
|
+
const steerEvents = (this.steers.get(streamId) ?? []).length ? this.finalizeSteers(streamId, leftover, 'stopped').events : [];
|
|
1178
1231
|
if (sessionId) {
|
|
1179
1232
|
let current = null;
|
|
1180
1233
|
try {
|
|
@@ -1210,6 +1263,21 @@ export class TurnRunner {
|
|
|
1210
1263
|
this.registry.liveIds.delete(streamId);
|
|
1211
1264
|
}
|
|
1212
1265
|
this.abortControllers.get(streamId)?.abort();
|
|
1266
|
+
const withdrawn = steerEvents.filter(([event]) => event === 'steer_withdrawn').map(([, data]) => data);
|
|
1267
|
+
return { cancelled: true, withdrawn };
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* TAL-460: a user's message never joins a background turn. That turn stops quietly (`finalizeCancelled`) and its worker
|
|
1271
|
+
* unwinds before the user's turn is admitted, so two Agent runs never share the session. True when one was stopped.
|
|
1272
|
+
*/
|
|
1273
|
+
async yieldBackgroundTurn(sessionId) {
|
|
1274
|
+
const streamId = this.registry.activeRunStreamForSession(sessionId);
|
|
1275
|
+
if (!streamId || this.registry.activeRuns.get(streamId)?.origin !== 'background')
|
|
1276
|
+
return false;
|
|
1277
|
+
await this.cancel(streamId);
|
|
1278
|
+
let timer;
|
|
1279
|
+
await Promise.race([this.workers.get(streamId), new Promise((resolve) => { timer = setTimeout(resolve, BACKGROUND_UNWIND_WAIT_MS); })]);
|
|
1280
|
+
clearTimeout(timer);
|
|
1213
1281
|
return true;
|
|
1214
1282
|
}
|
|
1215
1283
|
async steer(sessionId, text, displayText, steerId) {
|
|
@@ -1234,7 +1302,10 @@ export class TurnRunner {
|
|
|
1234
1302
|
return { accepted: false, fallback: 'no_cached_agent', stream_id: null };
|
|
1235
1303
|
// Register provisionally before the RPC: the Agent may apply the steer and finish the turn before the steer
|
|
1236
1304
|
// reply arrives, and the turn's finalisation must then already see the record. A rejection removes it again.
|
|
1237
|
-
|
|
1305
|
+
return this.withSteerChain(activeStreamId, () => this.steerInto(sidecar, sessionId, activeStreamId, text, displayText, steerId));
|
|
1306
|
+
}
|
|
1307
|
+
async steerInto(sidecar, sessionId, activeStreamId, text, displayText, steerId) {
|
|
1308
|
+
const record = { steer_id: steerId, session_id: sessionId, stream_id: activeStreamId, text, display_text: displayText || text, created_at: this.deps.now(), state: 'pending', can_redirect: false };
|
|
1238
1309
|
const records = this.steers.get(activeStreamId) ?? [];
|
|
1239
1310
|
records.push(record);
|
|
1240
1311
|
this.steers.set(activeStreamId, records);
|
|
@@ -1259,6 +1330,12 @@ export class TurnRunner {
|
|
|
1259
1330
|
// An accepted steer whose turn finalised while the reply was in flight was already reported by that finalisation.
|
|
1260
1331
|
if (!result.accepted)
|
|
1261
1332
|
withdraw();
|
|
1333
|
+
else {
|
|
1334
|
+
record.can_redirect = result.can_redirect === true;
|
|
1335
|
+
// TAL-424: every client shows it until the Agent takes it (unless it already did while the reply was in flight).
|
|
1336
|
+
if (this.steers.get(activeStreamId)?.includes(record))
|
|
1337
|
+
this.emitToSession(sessionId, 'steer_pending', this.pendingSteerView(record));
|
|
1338
|
+
}
|
|
1262
1339
|
return { accepted: result.accepted, fallback: result.accepted ? null : (result.fallback ?? 'not_running'), stream_id: activeStreamId, steer_id: steerId };
|
|
1263
1340
|
}
|
|
1264
1341
|
/** Python `_webui_steer_events_before` for terminal frames: cancel/apperror flush consumed steers and leftovers first. */
|
|
@@ -1267,7 +1344,7 @@ export class TurnRunner {
|
|
|
1267
1344
|
return [];
|
|
1268
1345
|
if (!(this.steers.get(streamId) ?? []).length)
|
|
1269
1346
|
return [];
|
|
1270
|
-
return this.
|
|
1347
|
+
return this.finalizeSteers(streamId, '', 'stopped').events;
|
|
1271
1348
|
}
|
|
1272
1349
|
/** Python `_pending_webui_steer_suffix_start`: the pending text is the newline-joined tail of the records; earlier ones are consumed. */
|
|
1273
1350
|
static pendingSteerSuffixStart(records, pendingText) {
|
|
@@ -1280,15 +1357,15 @@ export class TurnRunner {
|
|
|
1280
1357
|
}
|
|
1281
1358
|
return null;
|
|
1282
1359
|
}
|
|
1283
|
-
consumedSteerPayload(record) {
|
|
1360
|
+
consumedSteerPayload(record, opts = {}) {
|
|
1284
1361
|
const consumedAt = this.deps.now();
|
|
1285
1362
|
const afterToolCallId = this.lastCompletedTool.get(record.stream_id) ?? null;
|
|
1286
1363
|
const consumed = this.consumedSteers.get(record.stream_id) ?? [];
|
|
1287
1364
|
if (!consumed.some((c) => c.steer_id === record.steer_id)) {
|
|
1288
|
-
consumed.push({ steer_id: record.steer_id, text: record.display_text || record.text, agent_text: record.text, submitted_at: record.created_at, consumed_at: consumedAt, after_tool_call_id: afterToolCallId });
|
|
1365
|
+
consumed.push({ steer_id: record.steer_id, text: record.display_text || record.text, agent_text: record.text, submitted_at: record.created_at, consumed_at: consumedAt, after_tool_call_id: afterToolCallId, ...(opts.redirected ? { redirected: true } : {}) });
|
|
1289
1366
|
this.consumedSteers.set(record.stream_id, consumed);
|
|
1290
1367
|
}
|
|
1291
|
-
return {
|
|
1368
|
+
return { steer_id: record.steer_id, session_id: record.session_id, stream_id: record.stream_id, display_text: record.display_text, created_at: record.created_at, agent_text: record.text, text: record.display_text || record.text, consumed_at: consumedAt, after_tool_call_id: afterToolCallId };
|
|
1292
1369
|
}
|
|
1293
1370
|
/**
|
|
1294
1371
|
* Consumed steers become display-only `_steer` user rows at their causal place in the turn: after the tool results of
|
|
@@ -1309,7 +1386,14 @@ export class TurnRunner {
|
|
|
1309
1386
|
};
|
|
1310
1387
|
// An Agent that records the steer it delivered does so at its exact place: that row becomes the steer, not a copy.
|
|
1311
1388
|
const agentRows = s.messages.flatMap((m, i) => (m._turn_id === streamId && agentSteerText(m) !== null ? [i] : []));
|
|
1312
|
-
|
|
1389
|
+
let agentRow = agentRows.find((i) => agentSteerText(s.messages[i]) === steer.agent_text.trim()) ?? agentRows[0];
|
|
1390
|
+
// A Send now redirect is recorded as a plain user row of the turn: that row becomes the steer too.
|
|
1391
|
+
if (steer.redirected) {
|
|
1392
|
+
const prompt = s.messages.findIndex((m) => m._turn_id === streamId && m.role === 'user' && !m._steer);
|
|
1393
|
+
const own = s.messages.findIndex((m, i) => i > prompt && m._turn_id === streamId && m.role === 'user' && !m._steer && messageText(m.content).trim() === steer.agent_text.trim());
|
|
1394
|
+
// Never another steer's row: without its own correction row (the Agent joins two redirects) it gets a new one.
|
|
1395
|
+
agentRow = own >= 0 ? own : undefined;
|
|
1396
|
+
}
|
|
1313
1397
|
if (agentRow === undefined)
|
|
1314
1398
|
s.messages.splice(steerInsertIndex(s.messages, streamId, steer.after_tool_call_id), 0, row);
|
|
1315
1399
|
else
|
|
@@ -1364,22 +1448,181 @@ export class TurnRunner {
|
|
|
1364
1448
|
return [];
|
|
1365
1449
|
return records.slice(0, start).map((r) => this.consumedSteerPayload(r));
|
|
1366
1450
|
}
|
|
1367
|
-
/**
|
|
1368
|
-
|
|
1451
|
+
/**
|
|
1452
|
+
* Terminal steer bookkeeping: the records the Agent no longer holds are consumed; the rest it never took are either
|
|
1453
|
+
* `stopped` (withdrawn with their text for the composer) or returned as one `followup` turn's texts (TAL-424).
|
|
1454
|
+
*/
|
|
1455
|
+
finalizeSteers(streamId, leftoverText, mode) {
|
|
1369
1456
|
const records = this.steers.get(streamId) ?? [];
|
|
1370
1457
|
this.steers.delete(streamId);
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const
|
|
1375
|
-
const
|
|
1376
|
-
for (const r of leftovers)
|
|
1377
|
-
events.push(['pending_steer_leftover', { ...r, leftover_at: this.deps.now() }]);
|
|
1378
|
-
const matched = leftovers.map((r) => r.text).join('\n');
|
|
1458
|
+
// Text that ends with none of these steers is another surface's: the Agent took every one of them.
|
|
1459
|
+
const start = TurnRunner.pendingSteerSuffixStart(records, leftoverText) ?? records.length;
|
|
1460
|
+
const consumed = records.slice(0, start);
|
|
1461
|
+
const left = records.slice(start);
|
|
1462
|
+
const matched = left.map((r) => r.text).join('\n');
|
|
1379
1463
|
const unmatched = matched ? leftoverText.slice(0, leftoverText.length - matched.length).replace(/\n+$/, '') : leftoverText;
|
|
1464
|
+
const events = consumed.map((r) => ['steer_consumed', this.consumedSteerPayload(r)]);
|
|
1465
|
+
for (const r of left)
|
|
1466
|
+
events.push(['steer_withdrawn', { steer_id: r.steer_id, reason: mode, text: r.display_text }]);
|
|
1467
|
+
if (mode === 'followup')
|
|
1468
|
+
return { events, leftovers: [...(unmatched ? [unmatched] : []), ...left.map((r) => r.text)] };
|
|
1469
|
+
if (unmatched)
|
|
1470
|
+
events.push(['steer_withdrawn', { steer_id: null, reason: 'stopped', text: unmatched }]);
|
|
1471
|
+
// ponytail: legacy `pending_steer_leftover` keeps today's Web and App from losing a stopped steer (they requeue it);
|
|
1472
|
+
// drop it once TAL-425 / TAL-426 put `steer_withdrawn` text back in the composer.
|
|
1473
|
+
for (const r of left)
|
|
1474
|
+
events.push(['pending_steer_leftover', { steer_id: r.steer_id, stream_id: streamId, text: r.text, display_text: r.display_text, leftover_at: this.deps.now() }]);
|
|
1380
1475
|
if (unmatched)
|
|
1381
1476
|
events.push(['pending_steer_leftover', { stream_id: streamId, text: unmatched }]);
|
|
1382
|
-
return events;
|
|
1477
|
+
return { events, leftovers: [] };
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* TAL-424: the steers a finished turn never took go on as one follow-up turn, sent once by the server for every client.
|
|
1481
|
+
* Should another turn already hold the session, they steer into that one instead, so no text is lost.
|
|
1482
|
+
*/
|
|
1483
|
+
startSteerFollowUp(sessionId, texts) {
|
|
1484
|
+
if (!texts.length)
|
|
1485
|
+
return;
|
|
1486
|
+
let s;
|
|
1487
|
+
try {
|
|
1488
|
+
s = this.deps.store.get(sessionId);
|
|
1489
|
+
}
|
|
1490
|
+
catch {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
const msg = texts.join('\n');
|
|
1494
|
+
const started = this.start(s, { msg, attachments: [], workspace: s.workspace, model: s.model, modelProvider: s.model_provider });
|
|
1495
|
+
if (!started._status || started._status < 400)
|
|
1496
|
+
return;
|
|
1497
|
+
if (started._status === 409 && started.active_stream_id) {
|
|
1498
|
+
void this.steer(sessionId, msg, msg, `steer-${randomUUID()}`).then((r) => {
|
|
1499
|
+
if (r.accepted !== true)
|
|
1500
|
+
this.deps.log(`[webui] WARNING: steer follow-up for ${sessionId} was not accepted (${str(r.fallback)}); text: ${msg.slice(0, 200)}`);
|
|
1501
|
+
});
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
this.deps.log(`[webui] WARNING: steer follow-up for ${sessionId} not started (${String(started._status)}): ${str(started.error)}`);
|
|
1505
|
+
}
|
|
1506
|
+
/** The Agent's pending steer text settles which steers it has taken: those become `steer_consumed` and their rows. */
|
|
1507
|
+
settleConsumedSteers(sessionId, streamId, pendingText) {
|
|
1508
|
+
for (const record of this.takeConsumedSteers(streamId, pendingText, { keepLeftovers: true }))
|
|
1509
|
+
this.emitToSession(sessionId, 'steer_consumed', record);
|
|
1510
|
+
this.saveConsumedSteers(sessionId, streamId);
|
|
1511
|
+
}
|
|
1512
|
+
/** What every client shows for a pending steer; the server decides which actions it offers. */
|
|
1513
|
+
pendingSteerView(r) {
|
|
1514
|
+
const open = r.state === 'pending';
|
|
1515
|
+
return { steer_id: r.steer_id, text: r.display_text, submitted_at: r.created_at, state: r.state, actions: { edit: open, cancel: open, send_now: open && r.can_redirect } };
|
|
1516
|
+
}
|
|
1517
|
+
/** TAL-424: the active stream's pending steers, oldest first (session detail `pending_steers`). */
|
|
1518
|
+
pendingSteers(streamId) {
|
|
1519
|
+
return (this.steers.get(streamId) ?? []).map((r) => this.pendingSteerView(r));
|
|
1520
|
+
}
|
|
1521
|
+
withSteerChain(streamId, fn) {
|
|
1522
|
+
const run = (this.steerChains.get(streamId) ?? Promise.resolve()).then(fn);
|
|
1523
|
+
const settled = run.catch(() => undefined).then(() => { if (this.steerChains.get(streamId) === settled)
|
|
1524
|
+
this.steerChains.delete(streamId); });
|
|
1525
|
+
this.steerChains.set(streamId, settled);
|
|
1526
|
+
return run;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* A withdraw or Send now on one pending steer of the session's active stream. `rewrite` asks the sidecar to change the
|
|
1530
|
+
* Agent's queue and says whether it did; pending text the Agent reported meanwhile is settled after, unless the queue
|
|
1531
|
+
* changed (the next report then reflects it).
|
|
1532
|
+
*/
|
|
1533
|
+
async rewriteSteer(sessionId, steerId, fallback, rewrite) {
|
|
1534
|
+
let s = null;
|
|
1535
|
+
try {
|
|
1536
|
+
s = this.deps.store.get(sessionId, { metadataOnly: true });
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
s = null;
|
|
1540
|
+
}
|
|
1541
|
+
const streamId = s?.active_stream_id;
|
|
1542
|
+
const sidecar = this.deps.sidecar();
|
|
1543
|
+
if (!streamId || !sidecar)
|
|
1544
|
+
return fallback;
|
|
1545
|
+
return this.withSteerChain(streamId, async () => {
|
|
1546
|
+
const records = this.steers.get(streamId) ?? [];
|
|
1547
|
+
const index = records.findIndex((r) => r.steer_id === steerId);
|
|
1548
|
+
if (index < 0 || records[index].state !== 'pending')
|
|
1549
|
+
return fallback;
|
|
1550
|
+
// In flight before the sidecar call, so a pending-text report racing its reply is deferred too.
|
|
1551
|
+
let finished = () => undefined;
|
|
1552
|
+
this.steerRewrites.set(streamId, new Promise((resolve) => { finished = resolve; }));
|
|
1553
|
+
let outcome = { result: fallback, changed: false };
|
|
1554
|
+
try {
|
|
1555
|
+
outcome = await rewrite(sidecar, streamId, records, index);
|
|
1556
|
+
}
|
|
1557
|
+
catch {
|
|
1558
|
+
outcome = { result: fallback, changed: false };
|
|
1559
|
+
}
|
|
1560
|
+
finally {
|
|
1561
|
+
this.steerRewrites.delete(streamId);
|
|
1562
|
+
finished();
|
|
1563
|
+
const deferred = this.deferredPendingSteer.get(streamId);
|
|
1564
|
+
this.deferredPendingSteer.delete(streamId);
|
|
1565
|
+
if (deferred !== undefined && !outcome.changed)
|
|
1566
|
+
this.settleConsumedSteers(sessionId, streamId, deferred);
|
|
1567
|
+
}
|
|
1568
|
+
return outcome.result;
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
dropSteer(streamId, record) {
|
|
1572
|
+
const records = this.steers.get(streamId);
|
|
1573
|
+
const index = records?.indexOf(record) ?? -1;
|
|
1574
|
+
if (records && index >= 0)
|
|
1575
|
+
records.splice(index, 1);
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* TAL-424 Edit and Cancel: take a pending steer back before the Agent takes it. `withdrawn: false` (and nothing
|
|
1579
|
+
* emitted) when it is unknown, already taken or being sent; a taken one then settles as consumed.
|
|
1580
|
+
*/
|
|
1581
|
+
withdrawSteer(sessionId, steerId, reason) {
|
|
1582
|
+
return this.rewriteSteer(sessionId, steerId, { withdrawn: false }, async (sidecar, streamId, records, index) => {
|
|
1583
|
+
const record = records[index];
|
|
1584
|
+
const reply = await sidecar.call('chat.steer_withdraw', { stream_id: streamId, pending: records.map((r) => r.text), index });
|
|
1585
|
+
if (!reply.withdrawn)
|
|
1586
|
+
return { result: { withdrawn: false }, changed: false };
|
|
1587
|
+
this.dropSteer(streamId, record);
|
|
1588
|
+
this.emitToSession(sessionId, 'steer_withdrawn', { steer_id: record.steer_id, reason, text: record.display_text });
|
|
1589
|
+
return { result: { withdrawn: true, text: record.display_text }, changed: true };
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* TAL-424 Send now: deliver a pending steer with the Agent's redirect. A model request restarts with it (consumed at
|
|
1594
|
+
* once); during tools it goes last on the Agent's queue and stays `sending_now` until taken; with nothing live it
|
|
1595
|
+
* stays pending.
|
|
1596
|
+
*/
|
|
1597
|
+
sendSteerNow(sessionId, steerId) {
|
|
1598
|
+
return this.rewriteSteer(sessionId, steerId, { redirected: false }, async (sidecar, streamId, records, index) => {
|
|
1599
|
+
const record = records[index];
|
|
1600
|
+
if (!record.can_redirect)
|
|
1601
|
+
return { result: { redirected: false }, changed: false };
|
|
1602
|
+
const reply = await sidecar.call('chat.steer_now', { stream_id: streamId, pending: records.map((r) => r.text), index });
|
|
1603
|
+
if (!reply.withdrawn)
|
|
1604
|
+
return { result: { redirected: false }, changed: false };
|
|
1605
|
+
if (!reply.redirected) {
|
|
1606
|
+
// The Agent's queue changed meanwhile, so it went back last: clients follow the new order.
|
|
1607
|
+
if (reply.requeued === 'last') {
|
|
1608
|
+
this.dropSteer(streamId, record);
|
|
1609
|
+
records.push(record);
|
|
1610
|
+
this.emitToSession(sessionId, 'steer_pending', this.pendingSteerView(record));
|
|
1611
|
+
}
|
|
1612
|
+
return { result: { redirected: false }, changed: true };
|
|
1613
|
+
}
|
|
1614
|
+
this.dropSteer(streamId, record);
|
|
1615
|
+
if (reply.delivery === 'steer') {
|
|
1616
|
+
record.state = 'sending_now';
|
|
1617
|
+
records.push(record);
|
|
1618
|
+
this.emitToSession(sessionId, 'steer_pending', this.pendingSteerView(record));
|
|
1619
|
+
}
|
|
1620
|
+
else {
|
|
1621
|
+
this.emitToSession(sessionId, 'steer_consumed', this.consumedSteerPayload(record, { redirected: true }));
|
|
1622
|
+
this.saveConsumedSteers(sessionId, streamId);
|
|
1623
|
+
}
|
|
1624
|
+
return { result: { redirected: true }, changed: true };
|
|
1625
|
+
});
|
|
1383
1626
|
}
|
|
1384
1627
|
// ── approvals / clarify ──────────────────────────────────────────────────
|
|
1385
1628
|
async respondApproval(sessionId, choice, approvalId, enableYolo) {
|