@maudecode/talaria-web 0.0.0 → 1.12.1
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 +20 -0
- package/dist/api/chat-router.d.ts +120 -3
- package/dist/api/chat-router.js +11 -8
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/router.d.ts +10 -0
- package/dist/api/router.js +2 -1
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +691 -40
- package/dist/api/sessions-router.js +2 -2
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +8 -0
- package/dist/api/settings-router.js +13 -5
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +53 -9
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +420 -0
- package/dist/api/tools-router.js +98 -5
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +1247 -43
- package/dist/auth/oidc.js +2 -0
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +17 -0
- package/dist/auth/store.js +77 -19
- package/dist/auth/store.js.map +1 -1
- package/dist/bin/talaria-web.js +4 -1
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/supervise.js +2 -1
- package/dist/cli/supervise.js.map +1 -1
- package/dist/fs/atomic.d.ts +8 -1
- package/dist/fs/atomic.js +52 -3
- package/dist/fs/atomic.js.map +1 -1
- package/dist/http/context.d.ts +2 -0
- package/dist/http/context.js.map +1 -1
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +1393 -9
- package/dist/redact.js.map +1 -1
- package/dist/release.js +3 -1
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +28 -11
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +11 -5
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +59 -5
- package/dist/sessions/anchor.js +373 -32
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/file-changes.d.ts +15 -0
- package/dist/sessions/file-changes.js +58 -0
- package/dist/sessions/file-changes.js.map +1 -0
- package/dist/sessions/journal.d.ts +6 -1
- package/dist/sessions/journal.js +9 -15
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +21 -1
- package/dist/sessions/list.js +81 -17
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +72 -2
- package/dist/sessions/merge.js +326 -13
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +11 -2
- package/dist/sessions/pending.js +52 -3
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/service.d.ts +18 -3
- package/dist/sessions/service.js +115 -62
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.js +1 -1
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/state-db.d.ts +5 -0
- package/dist/sessions/state-db.js +34 -0
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/streams.d.ts +4 -1
- package/dist/sessions/streams.js +3 -0
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +21 -0
- package/dist/sessions/tool-display.js +113 -0
- package/dist/sessions/tool-display.js.map +1 -0
- package/dist/sessions/turn.d.ts +39 -7
- package/dist/sessions/turn.js +288 -98
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/window.d.ts +1 -0
- package/dist/sessions/window.js +1 -1
- package/dist/sessions/window.js.map +1 -1
- package/dist/settings.js +5 -0
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +1 -1
- package/dist/sidecar/client.js +15 -4
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/fake.js +3 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.d.ts +2 -0
- package/dist/spa.js +9 -0
- package/dist/spa.js.map +1 -1
- package/dist/test/harness.d.ts +2 -0
- package/dist/test/harness.js +1 -1
- package/dist/test/harness.js.map +1 -1
- package/dist/tools/crons.js +55 -43
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/terminal.d.ts +6 -0
- package/dist/tools/terminal.js +24 -3
- package/dist/tools/terminal.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +120 -0
- package/dist/tools/update-notifications.js +387 -0
- package/dist/tools/update-notifications.js.map +1 -0
- package/dist/tools/updates.d.ts +97 -13
- package/dist/tools/updates.js +638 -124
- package/dist/tools/updates.js.map +1 -1
- package/package.json +2 -2
- package/sidecar/agent_dependency.json +4 -3
- package/sidecar/scripts/replay_sidecar.py +17 -0
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +51 -19
- package/sidecar/talaria_sidecar/methods/chat.py +113 -4
- package/sidecar/talaria_sidecar/methods/config.py +5 -3
- package/sidecar/talaria_sidecar/runtime.py +3 -9
- package/static/dist/FILES.txt +115 -116
- package/static/dist/assets/AppShell-m4w420jl.js +1 -0
- package/static/dist/assets/ChatPage-cuqcnisv.js +20 -0
- package/static/dist/assets/HubRoute-hqgqtm35.js +2 -0
- package/static/dist/assets/{Markdown-bt9bqd6a.js → Markdown-j2hlu30v.js} +7 -7
- package/static/dist/assets/RightPanel-oid5q557.js +1 -0
- package/static/dist/assets/Select-ot2zr0il.js +1 -0
- package/static/dist/assets/SessionListPanel-o3sgjnrq.js +1 -0
- package/static/dist/assets/SettingsLayout-b0idn649.js +1 -0
- package/static/dist/assets/States-htz1oee5.js +1 -0
- package/static/dist/assets/Toaster-h49pk1hy.js +1 -0
- package/static/dist/assets/_app-h7eaezs7.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-h8oaqzsj.js +1 -0
- package/static/dist/assets/_app.index-xinao3xh.js +1 -0
- package/static/dist/assets/_app.insights-cxplvhkm.js +1 -0
- package/static/dist/assets/_app.kanban-hhli8h0h.js +1 -0
- package/static/dist/assets/_app.logs-f6srwjfl.js +1 -0
- package/static/dist/assets/_app.memory-lntcea85.js +1 -0
- package/static/dist/assets/_app.profiles-lftyp6of.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-kevi4qd2.js → _app.session._sessionId-hhpcqih4.js} +1 -1
- package/static/dist/assets/_app.settings-rs93zpvw.js +1 -0
- package/static/dist/assets/_app.settings._section-nafqsbwm.js +1 -0
- package/static/dist/assets/_app.skills-jzoi1ivd.js +1 -0
- package/static/dist/assets/_app.tasks-ijphsw56.js +2 -0
- package/static/dist/assets/_app.todos-emyh8inv.js +1 -0
- package/static/dist/assets/_app.workspaces-fwr1cum8.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-hvrr25lb.js → abnfDiagram-VCTEODGH-egykbz8h.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-l6y92mbn.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-b3r0z8sq.js → architectureDiagram-5GKGNRK7-b2e0wk3y.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-hd3gw0cj.js → blockDiagram-I7D4REHJ-i3cq67dn.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-jlmbjiji.js → c4Diagram-7LVT6UL2-lg1p1s7a.js} +1 -1
- package/static/dist/assets/channel-gq8cwu71.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-oydbpsin.js → chunk-4HAMMTFA-i0s1s7zi.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-j633otj1.js → chunk-75Z2AOVW-kwinpapo.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-ods2l027.js → chunk-DU6HZSFF-hb8qrbc3.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-ln4n2cwt.js → chunk-F27PBJKO-dy2tqtcs.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-fpz133eh.js → chunk-GMAD6QVW-fqox5yvt.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-abs9ixiy.js → chunk-GVQU2GXP-bo3x6ze4.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-j9vukdy1.js → chunk-IMKFNOWR-nxs6rrcq.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-cfyw87r3.js → chunk-L3NEJ4N5-f9yeq1fi.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-baja78wr.js → chunk-OSK3NFVY-c7df44qv.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-g66xby8h.js → chunk-P2QGCYS3-eyjzf5o5.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-osqu864t.js → chunk-POPQ4Y6H-iy3hp9rc.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-egejasv1.js → chunk-PWAF6VOD-co2yoefz.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-l014h8zz.js → chunk-SHT3W25Y-obzqmn9k.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-n3z10bp3.js → chunk-SVP7TREG-hqwdo6vt.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-bzozkjc0.js → chunk-TICWLB2K-l8t1mdfd.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-fg9sc5r8.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-fg9sc5r8.js +1 -0
- package/static/dist/assets/{copied-cjsttec1.js → copied-hurn8vn4.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-elpt7h1j.js → cynefin-OW5HDTMX-ngx2ihay.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-5mzl5pi2.js → cynefinDiagram-5FMLGOSQ-fiyooys3.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-ihwlhmfl.js → dagre-GXQ25YYZ-fctryel6.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-4z90agaz.js → diagram-S7CK7UJ4-m27jopok.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-eifw0lg7.js → diagram-UQ7AKVKN-ojgkbmp4.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-ksh3sjqv.js → diagram-VSXAHHWV-gfafpgyj.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-ett72f71.js → diagram-VX7I27RA-daobicg4.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-l8scp8nu.js → diagram-Z3DM3KII-mi475zvk.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-f0pxcmit.js → ebnfDiagram-PWID7BFC-daf4zvsy.js} +1 -1
- package/static/dist/assets/endpoints-cyjhnrbn.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-f47m5xh0.js → erDiagram-RLTQ6QDP-gxfhxvfp.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-manga0bq.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-bpt13uny.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-bmktjdi0.js → ganttDiagram-EL5Y4UJY-k3208e0p.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-j2ueg258.js → gitGraph-4MIJSDKK-gjuhj4zk.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-ms6586v9.js → gitGraphDiagram-WWUBYQGX-jglxvux1.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-ev52d2si.js → highlighted-body-KPVGNVTW-gh24xc9c.js} +1 -1
- package/static/dist/assets/index-gsg78cb4.css +2 -0
- package/static/dist/assets/{index-hywfjw88.js → index-jb47los5.js} +4 -4
- package/static/dist/assets/{info-A6RAGUB7-j717vyt7.js → info-A6RAGUB7-huf0xaoq.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-dsbpib36.js → infoDiagram-27XIBGKW-jk3n96ix.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-n0sz3i2d.js → ishikawaDiagram-5VMMS53U-o95uo5dx.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-mcxz1mme.js → journeyDiagram-3NMN7TZE-h5ye2l70.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-n6hq2lyc.js → kanban-definition-UXKFOSKX-lgrpk5yi.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-faquioun.js → lazyRouteComponent-owywngyt.js} +1 -1
- package/static/dist/assets/{line-h5xq20s4.js → line-cihmc0pn.js} +1 -1
- package/static/dist/assets/login-g063apx6.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-okd1mnvj.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-n9cuu5ue.js → mermaid-parser.core-d1609aga.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-gta3u7z4.js → mindmap-definition-YA3MSWOX-lbvtir31.js} +1 -1
- package/static/dist/assets/onboarding-ngw0x0pr.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-bpzuq5n6.js → packet-AYTQ26CC-o4cp5z3k.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-juye13e5.js → pegDiagram-XKGWAZYB-e8xqu1qj.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-odhb8buq.js → pie-WAS4IAKB-hm9icuov.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-mgb14ksh.js → pieDiagram-E7YTZNPT-hj96x7hn.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-cg36naog.js → quadrantDiagram-AXDQQJYC-80utbxhm.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-lgzfpnef.js → radar-RG4KPBEZ-k39b6100.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-gmglc3ua.js → railroad-74A4TZTK-ewyqz67j.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-dx2q0evz.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-l8xt49t5.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-kuxha9yz.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-cmxwjt73.js → railroadDiagram-O6MQD6OU-iwv9dby8.js} +1 -1
- package/static/dist/assets/{refresh-cw-mf192q3f.js → refresh-cw-hjzomwlo.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-ir0s2mek.js → requirementDiagram-BXWQKSXE-cdgh5jha.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jo7jcdq0.js → sankeyDiagram-P5KCCOFB-dbu6p97m.js} +1 -1
- package/static/dist/assets/{saved-g777hp1t.js → saved-feqhycco.js} +1 -1
- package/static/dist/assets/{search-i2kfogyx.js → search-n3jdc20p.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-n0pf6xk5.js → sequenceDiagram-WJ2MYXX4-b0wchifg.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-hgvh507l.js → settings_section_extensions_title-mtyviexd.js} +1 -1
- package/static/dist/assets/share._token-cwov8uqh.js +2 -0
- package/static/dist/assets/sliders-horizontal-i1lseyg0.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-olbrwyv9.js → stateDiagram-D77RDMKH-mb4b38xh.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-cn1fnqrg.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-od14zrw6.js → swimlanes-42K2YHIH-ofrpfwj0.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d652ummv.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-l0qokmnb.js → timeline-definition-24CTP7MA-hlw0jg6o.js} +1 -1
- package/static/dist/assets/todoStore-mfy9lpio.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-hma3ip8m.js → treeView-Q6P3EWNA-d93wr19c.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-bghjng5m.js → treemap-WGGIJYW6-g7ezdedz.js} +1 -1
- package/static/dist/assets/{untitled-nfaw0q7m.js → untitled-mqxhd1eh.js} +1 -1
- package/static/dist/assets/{useForm-k7fj2zsr.js → useForm-hh33yrwa.js} +1 -1
- package/static/dist/assets/useLocale-cr30vl7r.js +1 -0
- package/static/dist/assets/{vennDiagram-4TSXK5OY-bo97zf3z.js → vennDiagram-4TSXK5OY-m5vgeba5.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-pbjl6jav.js → wardley-WFR3VGLG-gpao9bxv.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-bmy9i09l.js → wardleyDiagram-VM6X3IG4-g2yzwshm.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-eercu0mc.js → xychartDiagram-S5SC5T6Z-itakhpic.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +114 -152
- package/static/dist/assets/AppShell-gs0grlbs.js +0 -1
- package/static/dist/assets/ChatPage-gi1xlz52.js +0 -18
- package/static/dist/assets/HubRoute-qs9vzuh0.js +0 -2
- package/static/dist/assets/RightPanel-dn19xu42.js +0 -1
- package/static/dist/assets/Select-y7wtf85g.js +0 -1
- package/static/dist/assets/SessionListPanel-igwdj9xr.js +0 -1
- package/static/dist/assets/SettingsLayout-n6mu0ghg.js +0 -1
- package/static/dist/assets/States-oil2issf.js +0 -1
- package/static/dist/assets/Toaster-jtn59pjm.js +0 -1
- package/static/dist/assets/_app-k4jrkgfa.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-mmf1mepm.js +0 -1
- package/static/dist/assets/_app.index-mb41nf00.js +0 -1
- package/static/dist/assets/_app.insights-mz5cep8m.js +0 -1
- package/static/dist/assets/_app.kanban-ocgw506i.js +0 -1
- package/static/dist/assets/_app.logs-lstmoju6.js +0 -1
- package/static/dist/assets/_app.memory-dishj9ra.js +0 -1
- package/static/dist/assets/_app.profiles-dynpvsio.js +0 -1
- package/static/dist/assets/_app.settings-c2czm9v4.js +0 -1
- package/static/dist/assets/_app.settings._section-exwflld7.js +0 -1
- package/static/dist/assets/_app.skills-j5jh2nug.js +0 -1
- package/static/dist/assets/_app.tasks-b99d98fo.js +0 -2
- package/static/dist/assets/_app.todos-ma8urvn3.js +0 -1
- package/static/dist/assets/_app.workspaces-l86cxeod.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-mqft9w2p.js +0 -1
- package/static/dist/assets/channel-ix8cxdld.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-c210y3il.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-c210y3il.js +0 -1
- package/static/dist/assets/endpoints-ipnv1hd4.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-d9b5a0ia.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-kfxxsyrk.js +0 -1
- package/static/dist/assets/index-bz7g7rhx.css +0 -2
- package/static/dist/assets/login-mvjys1ol.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-gmnkgf1n.js +0 -1
- package/static/dist/assets/onboarding-l6quy6gz.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-5mjl5wpv.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-bfram8v8.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-nwfczn88.js +0 -1
- package/static/dist/assets/share._token-il3y6js2.js +0 -2
- package/static/dist/assets/sliders-horizontal-kd99ffvl.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-dnzlmdpu.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ck0eqqdp.js +0 -8
- package/static/dist/assets/todoStore-la6uyqzj.js +0 -1
- package/static/dist/assets/trash-bxz1k9cw.js +0 -1
- package/static/dist/assets/useLocale-evhacmv2.js +0 -1
package/dist/sessions/turn.js
CHANGED
|
@@ -12,17 +12,19 @@ import { isWithin, resolvePathLikePython } from '../workspace/paths.js';
|
|
|
12
12
|
import { SidecarError } from '../sidecar/client.js';
|
|
13
13
|
import { HttpFailure, markSessionTitleGenerated } from './service.js';
|
|
14
14
|
import { StreamRegistry, SessionChannels } from './streams.js';
|
|
15
|
-
import { PendingPrompts } from './pending.js';
|
|
15
|
+
import { PendingPrompts, clarifyReply } from './pending.js';
|
|
16
16
|
import { RunJournal } from './journal.js';
|
|
17
17
|
import { Session, titleFrom } from './session.js';
|
|
18
|
-
import { buildActiveTurnToken, redactSessionData, redactString } from '../redact.js';
|
|
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';
|
|
21
21
|
import { messageWindowForDisplay, messagesForLimitedPayload, toolCallsForMessageWindow } from './window.js';
|
|
22
22
|
import { attachTodoState } from './todo.js';
|
|
23
|
+
import { withSessionWireFlags } from './list.js';
|
|
24
|
+
import { hydrateAnchorActivityScenes, turnTerminalState, withTurnIds } from './anchor.js';
|
|
23
25
|
import { persistentStateChanges, persistentStateSnapshot } from './state-saved.js';
|
|
24
26
|
import { maxIterationsFromConfig, maxTokensFromConfig, processWakeupMaxIterations, reasoningConfigFromConfig, webuiEphemeralSystemPrompt, workspaceSystemMessage } from './turn-context.js';
|
|
25
|
-
import { assistantReplyAddedAfterCurrentTurn, buildPartialMessage, extractToolCallsFromMessages, injectMaxIterationSummaryFallback, isContextCompressionMarker, isDict, mergeDisplayMessagesAfterAgentResult, messageIdentity, messageText, sanitizeMessagesForApi, sessionLacksFinalAssistantAnswer, splitThinkingFromContent, stripXmlToolCalls, workspaceContextPrefix } from './merge.js';
|
|
27
|
+
import { agentSteerText, assistantReplyAddedAfterCurrentTurn, buildPartialMessage, checkpointTurnStart, extractToolCallsFromMessages, injectMaxIterationSummaryFallback, isContextCompressionMarker, isDict, mergeDisplayMessagesAfterAgentResult, messageIdentity, messageText, pendingUserRow, sanitizeMessagesForApi, sessionLacksFinalAssistantAnswer, splitThinkingFromContent, stoppedTurnContext, stripXmlToolCalls, toolOutcome, withToolCallOutcomes, workspaceContextPrefix } from './merge.js';
|
|
26
28
|
import { fallbackTitleFromExchange, firstExchangeSnippets, isGenericFallbackTitle, latestExchangeSnippets, looksInvalidGeneratedTitle, sanitizeGeneratedTitle, titleLanguageMismatch, titlePrompts } from './titles.js';
|
|
27
29
|
import { str } from '../util.js';
|
|
28
30
|
export const CHAT_LOCK_WAIT_SECONDS = 2;
|
|
@@ -34,7 +36,7 @@ export function classifyProviderError(errStr, opts = {}) {
|
|
|
34
36
|
return { label: 'Authentication failed', type: 'auth_mismatch', hint: 'The selected model may not be supported by your configured provider or your API key is invalid. Run `hermes model` in your terminal to update credentials, then restart the WebUI.' };
|
|
35
37
|
const cancelled = ['cancelled by user', 'canceled by user', 'user cancelled', 'user canceled', 'task cancelled', 'task canceled', 'cancellederror'].some((k) => lower.includes(k));
|
|
36
38
|
if (cancelled)
|
|
37
|
-
return { label: 'Task cancelled', type: 'cancelled', hint:
|
|
39
|
+
return { label: 'Task cancelled', type: 'cancelled', hint: '' };
|
|
38
40
|
if (['interrupted by user', 'response interrupted', 'operation interrupted', 'operation was interrupted', 'operation aborted', 'request was aborted', 'aborterror'].some((k) => lower.includes(k))) {
|
|
39
41
|
return { label: 'Response interrupted', type: 'interrupted', hint: 'The run stopped before a provider response completed. If you did not cancel it, try again.' };
|
|
40
42
|
}
|
|
@@ -60,12 +62,11 @@ export function classifyProviderError(errStr, opts = {}) {
|
|
|
60
62
|
return { label: 'No response from provider', type: 'no_response', hint: 'The provider returned no content and no error. This often means a usage/rate limit was hit silently. Check provider status, switch providers via `hermes model`, or try again in a moment.' };
|
|
61
63
|
return { label: 'Error', type: 'error', hint: '' };
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
65
|
+
/** Error classifications that are turn outcomes in their own right; every other one ends the turn as `error`. */
|
|
66
|
+
const CLASSIFIED_OUTCOMES = new Set(['no_response', 'compression_exhausted', 'interrupted', 'cancelled']);
|
|
66
67
|
export function providerErrorPayload(message, errType, hint = '', redact = true) {
|
|
67
68
|
const safe = redact ? redactString(message).trim() : message;
|
|
68
|
-
const payload = { message: safe || message, type: errType };
|
|
69
|
+
const payload = { message: safe || message, type: errType, terminal_state: CLASSIFIED_OUTCOMES.has(errType) ? errType : 'error' };
|
|
69
70
|
if (hint)
|
|
70
71
|
payload.hint = hint;
|
|
71
72
|
if (safe)
|
|
@@ -146,8 +147,17 @@ export class TurnRunner {
|
|
|
146
147
|
yoloChains = new Map();
|
|
147
148
|
abortControllers = new Map();
|
|
148
149
|
steers = new Map();
|
|
150
|
+
consumedSteers = new Map();
|
|
151
|
+
lastCompletedTool = new Map();
|
|
149
152
|
/** Streams whose run completed (`done` emitted) and only await title work; a late cancel is a no-op for these. */
|
|
150
153
|
settledStreams = new Set();
|
|
154
|
+
/**
|
|
155
|
+
* TAL-364: what a Stop needs to write the turn's model context: its starting context, the prompt the Agent got, and
|
|
156
|
+
* whether a settlement already used a canonical checkpoint (which marks the stop boundary; later work never replaces it).
|
|
157
|
+
*/
|
|
158
|
+
/** 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
|
+
interrupts = new Map();
|
|
160
|
+
stopContexts = new Map();
|
|
151
161
|
constructor(deps) {
|
|
152
162
|
this.deps = deps;
|
|
153
163
|
}
|
|
@@ -156,6 +166,8 @@ export class TurnRunner {
|
|
|
156
166
|
/** Python `_start_chat_stream_for_session`: persist pending state, register the channel, launch the worker. */
|
|
157
167
|
start(session, opts) {
|
|
158
168
|
const s = session;
|
|
169
|
+
if (this.deps.updateInProgress?.())
|
|
170
|
+
return { error: 'Web is updating. Retry after it restarts.', _status: 503 };
|
|
159
171
|
if (this.deps.profileDeleting?.(s.profile ?? null))
|
|
160
172
|
return { error: `Profile '${str(s.profile)}' is being deleted.`, _status: 409 };
|
|
161
173
|
const locked = s.active_stream_id;
|
|
@@ -190,7 +202,7 @@ export class TurnRunner {
|
|
|
190
202
|
s.title = provisional;
|
|
191
203
|
}
|
|
192
204
|
if (this.deps.saveMode() === 'eager')
|
|
193
|
-
this.checkpointUserMessage(s, opts.msg, attachments, s.pending_started_at, source);
|
|
205
|
+
this.checkpointUserMessage(s, opts.msg, attachments, s.pending_started_at, source, streamId);
|
|
194
206
|
this.deps.store.save(s);
|
|
195
207
|
this.registry.writebackOwners.set(s.session_id, streamId);
|
|
196
208
|
if (wasHiddenEmpty)
|
|
@@ -206,7 +218,7 @@ export class TurnRunner {
|
|
|
206
218
|
void this.run(s.session_id, streamId, channel, opts).catch((error) => {
|
|
207
219
|
this.deps.log(`[webui] ERROR turn ${streamId} crashed\n${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
208
220
|
});
|
|
209
|
-
const response = { stream_id: streamId, session_id: s.session_id, pending_started_at: s.pending_started_at, turn_id:
|
|
221
|
+
const response = { stream_id: streamId, session_id: s.session_id, pending_started_at: s.pending_started_at, turn_id: streamId, title: s.title };
|
|
210
222
|
if (opts.normalizedModel && opts.model)
|
|
211
223
|
response.effective_model = opts.model;
|
|
212
224
|
if (opts.modelProvider)
|
|
@@ -214,21 +226,11 @@ export class TurnRunner {
|
|
|
214
226
|
return response;
|
|
215
227
|
}
|
|
216
228
|
/** Python `_checkpoint_user_message_for_eager_session_save`. */
|
|
217
|
-
checkpointUserMessage(s, msg, attachments, startedAt, source) {
|
|
229
|
+
checkpointUserMessage(s, msg, attachments, startedAt, source, turnId) {
|
|
218
230
|
const latest = s.messages[s.messages.length - 1];
|
|
219
231
|
if (latest?.role === 'user' && messageText(latest.content).split(/\s+/).join(' ') === msg.split(/\s+/).join(' '))
|
|
220
232
|
return;
|
|
221
|
-
|
|
222
|
-
const token = buildActiveTurnToken(s.active_stream_id, startedAt);
|
|
223
|
-
if (token)
|
|
224
|
-
user._active_turn_token = token;
|
|
225
|
-
if (source !== 'webui')
|
|
226
|
-
user._source = source;
|
|
227
|
-
if (typeof startedAt === 'number' && startedAt > 0)
|
|
228
|
-
user.timestamp = startedAt;
|
|
229
|
-
if (attachments.length)
|
|
230
|
-
user.attachments = [...attachments];
|
|
231
|
-
s.messages.push(user);
|
|
233
|
+
s.messages.push(pendingUserRow(msg, attachments, startedAt, source, turnId));
|
|
232
234
|
}
|
|
233
235
|
// ── worker ───────────────────────────────────────────────────────────────
|
|
234
236
|
async run(sessionId, streamId, channel, opts) {
|
|
@@ -241,7 +243,7 @@ export class TurnRunner {
|
|
|
241
243
|
catch (error) {
|
|
242
244
|
deps.log(`[webui] WARNING: run journal degraded for stream ${streamId}: ${error.message}`);
|
|
243
245
|
}
|
|
244
|
-
const put = (event, data) => {
|
|
246
|
+
const put = (event, data, meta = {}) => {
|
|
245
247
|
for (const steerEvent of this.takeSteerEventsBefore(streamId, event))
|
|
246
248
|
put(steerEvent[0], steerEvent[1]);
|
|
247
249
|
if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', 'pending_steer_leftover'].includes(event))
|
|
@@ -257,16 +259,20 @@ export class TurnRunner {
|
|
|
257
259
|
let eventId = null;
|
|
258
260
|
if (writer) {
|
|
259
261
|
try {
|
|
260
|
-
eventId = writer.appendSseEvent(event, data).event_id;
|
|
262
|
+
eventId = writer.appendSseEvent(event, data, meta).event_id;
|
|
261
263
|
}
|
|
262
264
|
catch (error) {
|
|
265
|
+
this.registry.degradedJournals.add(streamId);
|
|
263
266
|
deps.log(`[webui] WARNING: run journal append failed for ${streamId}/${event}: ${error.message}`);
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
|
-
channel.put([event, data, eventId]);
|
|
269
|
+
channel.put([event, data, eventId, meta.redacted]);
|
|
267
270
|
if (event === 'done' || event === 'cancel' || event === 'apperror' || event === 'error') {
|
|
271
|
+
// Relay's phase follows the turn outcome, so a background Live Activity ends as the app would end it.
|
|
272
|
+
const state = str(data.terminal_state);
|
|
273
|
+
const phase = state === 'completed' || state === 'tool_limit_reached' ? 'completed' : event === 'cancel' || state === 'cancelled' ? 'cancelled' : 'failed';
|
|
268
274
|
try {
|
|
269
|
-
deps.onTerminal?.(streamId,
|
|
275
|
+
deps.onTerminal?.(streamId, phase);
|
|
270
276
|
}
|
|
271
277
|
catch { /* best effort */ }
|
|
272
278
|
}
|
|
@@ -278,7 +284,7 @@ export class TurnRunner {
|
|
|
278
284
|
s = deps.store.get(sessionId);
|
|
279
285
|
}
|
|
280
286
|
catch {
|
|
281
|
-
put('apperror', { type: 'error', message: 'Session not found', session_id: sessionId });
|
|
287
|
+
put('apperror', { type: 'error', terminal_state: 'error', message: 'Session not found', session_id: sessionId });
|
|
282
288
|
this.teardown(sessionId, streamId);
|
|
283
289
|
return;
|
|
284
290
|
}
|
|
@@ -291,19 +297,32 @@ export class TurnRunner {
|
|
|
291
297
|
const previousContext = structuredClone(localContext.some((m) => isContextCompressionMarker(m)) ? localContext : deps.service().mergedTranscript(s, localContext));
|
|
292
298
|
// Python `_sanitize_messages_for_api`: the model never sees display-only rows or a replayed cancelled prompt.
|
|
293
299
|
const apiHistory = sanitizeMessagesForApi(previousContext);
|
|
300
|
+
const workspaceCtx = workspaceContextPrefix(opts.workspace);
|
|
301
|
+
// Before the first await: a Stop can land at any point after admission.
|
|
302
|
+
// An eager save already put this turn's prompt in the transcript; the Stop fallback appends it once itself.
|
|
303
|
+
this.stopContexts.set(streamId, { previousContext: previousContext.filter((m) => m._turn_id !== streamId), historyLength: apiHistory.length, prompt: workspaceCtx + msgText, msgText, checkpointed: false });
|
|
294
304
|
const activeTurnToken = buildActiveTurnToken(streamId, s.pending_started_at);
|
|
295
305
|
const sidecar = deps.sidecar();
|
|
296
306
|
const partialText = this.registry.partialText.get(streamId) ?? [];
|
|
297
307
|
const reasoningText = this.registry.reasoningText.get(streamId) ?? [];
|
|
298
308
|
const liveToolCalls = this.registry.liveToolCalls.get(streamId) ?? [];
|
|
309
|
+
// Each call's public `id`: the Agent's call id, else one minted here that its completion inherits through the pairing.
|
|
310
|
+
const toolIds = new WeakMap();
|
|
311
|
+
let mintedToolIds = 0;
|
|
312
|
+
const mintToolId = () => `tool-${streamId}-${String(++mintedToolIds)}`;
|
|
313
|
+
// When the server received each call's start frame: its completion's `duration` is measured from it.
|
|
314
|
+
const toolStartedAt = new WeakMap();
|
|
299
315
|
let tokenSent = false;
|
|
300
316
|
let firstTokenAt = null;
|
|
301
317
|
const titles = new ReasoningTitleTracker();
|
|
302
318
|
let capturedTerminalError = null;
|
|
303
319
|
const controller = new AbortController();
|
|
304
320
|
this.abortControllers.set(streamId, controller);
|
|
305
|
-
const workspaceCtx = workspaceContextPrefix(opts.workspace);
|
|
306
321
|
const userMessage = await this.buildUserMessage(workspaceCtx, msgText, opts.attachments ?? [], opts.workspace, sessionId, s, opts, controller.signal);
|
|
322
|
+
// The prompt the Agent actually gets, native image parts included.
|
|
323
|
+
const stop = this.stopContexts.get(streamId);
|
|
324
|
+
if (stop)
|
|
325
|
+
stop.prompt = userMessage;
|
|
307
326
|
if (activeRun)
|
|
308
327
|
activeRun.phase = 'running';
|
|
309
328
|
const settledAt = { value: false };
|
|
@@ -323,8 +342,8 @@ export class TurnRunner {
|
|
|
323
342
|
// Last yield before `chat.start`: a cancel that landed during the attachment, config or YOLO awaits must not
|
|
324
343
|
// start an Agent turn (the client also refuses an already-aborted signal outright).
|
|
325
344
|
if (this.registry.cancelled.has(streamId)) {
|
|
326
|
-
this.finalizeCancelled(s, streamId,
|
|
327
|
-
put('cancel', this.
|
|
345
|
+
this.finalizeCancelled(s, streamId, opts.ephemeral);
|
|
346
|
+
put('cancel', this.cancelFrame(sessionId));
|
|
328
347
|
return;
|
|
329
348
|
}
|
|
330
349
|
const frozenWorkspace = str(s.created_workspace) || str(s.workspace);
|
|
@@ -370,24 +389,34 @@ export class TurnRunner {
|
|
|
370
389
|
case 'steer_pending':
|
|
371
390
|
for (const record of this.takeConsumedSteers(streamId, str(data.text), { keepLeftovers: true }))
|
|
372
391
|
put('steer_consumed', record);
|
|
392
|
+
this.saveConsumedSteers(sessionId, streamId);
|
|
373
393
|
return;
|
|
374
|
-
case 'tool':
|
|
375
|
-
|
|
376
|
-
|
|
394
|
+
case 'tool': {
|
|
395
|
+
const call = { name: data.name, args: data.args ?? {}, tid: str(data.tid), done: false };
|
|
396
|
+
liveToolCalls.push(call);
|
|
397
|
+
toolIds.set(call, call.tid || mintToolId());
|
|
398
|
+
toolStartedAt.set(call, deps.now());
|
|
399
|
+
const redacted = deps.redactEnabled();
|
|
400
|
+
put('tool', publicToolFrame(withToolId(data, toolIds.get(call)), redacted), { redacted });
|
|
377
401
|
return;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
402
|
+
}
|
|
403
|
+
case 'tool_complete': {
|
|
404
|
+
// TAL-313: the server decides failure from the sidecar's raw result, which never leaves the server.
|
|
405
|
+
const { raw_result: rawResult, ...complete } = data;
|
|
406
|
+
complete.is_error = toolOutcome(rawResult).is_error;
|
|
407
|
+
const tc = liveToolCalls[completedToolIndex(liveToolCalls, str(data.tid), data.name)];
|
|
408
|
+
if (tc) {
|
|
409
|
+
const startedAt = toolStartedAt.get(tc);
|
|
410
|
+
if (startedAt !== undefined)
|
|
411
|
+
complete.duration = Math.round(Math.max(0, deps.now() - startedAt) * 1000) / 1000;
|
|
412
|
+
Object.assign(tc, { done: true, snippet: data.preview, is_error: complete.is_error, duration: complete.duration ?? null });
|
|
388
413
|
}
|
|
389
|
-
|
|
414
|
+
const id = (tc && toolIds.get(tc)) || str(data.tid) || mintToolId();
|
|
415
|
+
this.lastCompletedTool.set(streamId, id);
|
|
416
|
+
const redacted = deps.redactEnabled();
|
|
417
|
+
put('tool_complete', publicToolFrame(withToolId(complete, id), redacted), { redacted });
|
|
390
418
|
return;
|
|
419
|
+
}
|
|
391
420
|
// Python: the live chat frame carries the queue head plus depth, not the entry that just arrived.
|
|
392
421
|
case 'approval': {
|
|
393
422
|
deps.pending.submitApproval(sessionId, { ...data, session_id: sessionId });
|
|
@@ -419,7 +448,10 @@ export class TurnRunner {
|
|
|
419
448
|
});
|
|
420
449
|
settledAt.value = true;
|
|
421
450
|
if (this.registry.cancelled.has(streamId) || result.status === 'cancelled') {
|
|
422
|
-
|
|
451
|
+
// The Stop's pre-interrupt snapshot is the boundary, even when its reply lands after this result (the sidecar
|
|
452
|
+
// answers each request on its own thread); without one, the Agent's interrupted result is its canonical transcript.
|
|
453
|
+
const interrupted = await this.interrupts.get(streamId);
|
|
454
|
+
this.finalizeCancelled(s, streamId, opts.ephemeral, interrupted?.checkpoint ?? result.messages);
|
|
423
455
|
put('cancel', this.cancelFrame(sessionId));
|
|
424
456
|
return;
|
|
425
457
|
}
|
|
@@ -434,7 +466,7 @@ export class TurnRunner {
|
|
|
434
466
|
}
|
|
435
467
|
opts.onDone?.(answer);
|
|
436
468
|
// Python `_ephemeral_session_payload`: only role and content leave the server for a btw turn.
|
|
437
|
-
put('done', { session: { session_id: sessionId, messages: (result.messages).map((m) => ({ role: m.role, content: m.content })) }, usage: { input_tokens: 0, output_tokens: 0 }, ephemeral: true, answer });
|
|
469
|
+
put('done', { session: { session_id: sessionId, messages: (result.messages).map((m) => ({ role: m.role, content: m.content })) }, usage: { input_tokens: 0, output_tokens: 0 }, ephemeral: true, answer, terminal_state: answer.trim() ? 'completed' : 'no_response' });
|
|
438
470
|
try {
|
|
439
471
|
rmSync(deps.store.pathFor(sessionId), { force: true });
|
|
440
472
|
}
|
|
@@ -462,7 +494,7 @@ export class TurnRunner {
|
|
|
462
494
|
// sidecar reports `completed` whenever a failed run still carries messages).
|
|
463
495
|
// Python's second chance: a turn that emitted no new row still counts when the merged transcript it produced
|
|
464
496
|
// ends on a final answer (the current user row or trailing tool activity makes it "lacking").
|
|
465
|
-
const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now() });
|
|
497
|
+
const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId });
|
|
466
498
|
const assistantAdded = assistantReplyAddedAfterCurrentTurn(resultMessages, previousContext, msgText) || !sessionLacksFinalAssistantAnswer(mergedForCheck());
|
|
467
499
|
const lastErr = result.error ?? capturedTerminalError ?? '';
|
|
468
500
|
// Python `_turn_transcript_lacks_final_assistant_answer`: a partial result with no final answer is a silent failure even if tokens streamed.
|
|
@@ -471,16 +503,22 @@ export class TurnRunner {
|
|
|
471
503
|
const classification = classifyProviderError(lastErr, { silentFailure: !lastErr });
|
|
472
504
|
const errStr = lastErr || `${classification.label}.`;
|
|
473
505
|
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 stays a leftover.
|
|
507
|
+
const steerEvents = this.finalizeSteerEvents(streamId, str(result.pending_steer));
|
|
474
508
|
this.persistError(s, streamId, classification.label, payload, activeTurnToken);
|
|
475
509
|
payload.session = redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled());
|
|
476
510
|
payload.session_id = s.session_id;
|
|
477
511
|
payload.old_session_id = sessionId;
|
|
512
|
+
for (const [event, data] of steerEvents)
|
|
513
|
+
put(event, data);
|
|
478
514
|
put('apperror', payload);
|
|
479
515
|
failed = true;
|
|
480
516
|
return;
|
|
481
517
|
}
|
|
482
518
|
// ── settle the transcript ──
|
|
483
|
-
s
|
|
519
|
+
// The Agent's last pending-steer text settles the remaining steers before the turn is written back.
|
|
520
|
+
const steerEvents = this.finalizeSteerEvents(streamId, result.pending_steer);
|
|
521
|
+
s.messages = mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId });
|
|
484
522
|
s.context_messages = dedupeContext(resultMessages);
|
|
485
523
|
for (const m of s.messages) {
|
|
486
524
|
if (m.role !== 'assistant')
|
|
@@ -516,7 +554,7 @@ export class TurnRunner {
|
|
|
516
554
|
s.threshold_tokens = result.context.threshold_tokens;
|
|
517
555
|
if (typeof result.context.last_prompt_tokens === 'number')
|
|
518
556
|
s.last_prompt_tokens = result.context.last_prompt_tokens;
|
|
519
|
-
s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls);
|
|
557
|
+
s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls, s.tool_calls);
|
|
520
558
|
s.active_stream_id = null;
|
|
521
559
|
s.pending_user_message = null;
|
|
522
560
|
s.pending_attachments = [];
|
|
@@ -524,14 +562,13 @@ export class TurnRunner {
|
|
|
524
562
|
s.pending_user_source = null;
|
|
525
563
|
const attachments = opts.attachments ?? [];
|
|
526
564
|
if (attachments.length) {
|
|
527
|
-
const names = attachments.map((a) => str(a.name || a.filename)).filter(Boolean);
|
|
528
565
|
for (let i = s.messages.length - 1; i >= 0; i -= 1) {
|
|
529
566
|
const m = s.messages[i];
|
|
530
567
|
if (m.role === 'user') {
|
|
531
568
|
const content = messageText(m.content);
|
|
532
569
|
const base = msgText.includes('\n\n[Attached files:') ? (msgText.split('\n\n[Attached files:')[0] ?? '').trim() : msgText;
|
|
533
570
|
if (content.includes(base.slice(0, 60)) || msgText.includes(content.slice(0, 60)))
|
|
534
|
-
m.attachments =
|
|
571
|
+
m.attachments = [...attachments];
|
|
535
572
|
break;
|
|
536
573
|
}
|
|
537
574
|
}
|
|
@@ -565,9 +602,13 @@ export class TurnRunner {
|
|
|
565
602
|
m._turnTps = Math.round((usage.completion_tokens / duration) * 10) / 10;
|
|
566
603
|
if (result.model)
|
|
567
604
|
m._usedModel = result.model;
|
|
605
|
+
// The live `done` frame says the tool budget ran out; the persisted row says so too, so a reload agrees.
|
|
606
|
+
if (result.tool_limit_reached && m._turn_id === streamId)
|
|
607
|
+
m._terminal_state = 'tool_limit_reached';
|
|
568
608
|
break;
|
|
569
609
|
}
|
|
570
610
|
}
|
|
611
|
+
this.persistConsumedSteers(s, streamId, previousStartedAt(s, activeRun), now);
|
|
571
612
|
deps.store.save(s);
|
|
572
613
|
deps.pending.clearApprovals(sessionId);
|
|
573
614
|
deps.pending.clearClarifies(sessionId);
|
|
@@ -604,13 +645,12 @@ export class TurnRunner {
|
|
|
604
645
|
const donePayload = {
|
|
605
646
|
session: redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled()),
|
|
606
647
|
usage: doneUsage,
|
|
648
|
+
terminal_state: turnTerminalState(s.messages, streamId),
|
|
607
649
|
};
|
|
608
|
-
if (result.tool_limit_reached)
|
|
609
|
-
donePayload.terminal_state = 'tool_limit_reached';
|
|
650
|
+
if (result.tool_limit_reached)
|
|
610
651
|
donePayload.terminal_reason = 'max_iterations';
|
|
611
|
-
}
|
|
612
652
|
put('done', donePayload);
|
|
613
|
-
for (const [event, payload] of
|
|
653
|
+
for (const [event, payload] of steerEvents)
|
|
614
654
|
put(event, payload);
|
|
615
655
|
// The turn is over: release admission before the title work so a follow-up message is accepted while the
|
|
616
656
|
// (up to two) title prompts run; the channel and journal stay open for the `title` events (Python retired the
|
|
@@ -640,7 +680,7 @@ export class TurnRunner {
|
|
|
640
680
|
}
|
|
641
681
|
failed = true;
|
|
642
682
|
if (this.registry.cancelled.has(streamId)) {
|
|
643
|
-
this.finalizeCancelled(s, streamId,
|
|
683
|
+
this.finalizeCancelled(s, streamId, opts.ephemeral);
|
|
644
684
|
put('cancel', this.cancelFrame(sessionId));
|
|
645
685
|
return;
|
|
646
686
|
}
|
|
@@ -661,6 +701,8 @@ export class TurnRunner {
|
|
|
661
701
|
current = s;
|
|
662
702
|
}
|
|
663
703
|
if (current.active_stream_id === streamId) {
|
|
704
|
+
for (const [event, data] of this.takeSteerEventsBefore(streamId, 'apperror'))
|
|
705
|
+
put(event, data);
|
|
664
706
|
this.persistError(current, streamId, classification.label, payload, activeTurnToken);
|
|
665
707
|
payload.session = redactSessionData(this.terminalSessionPayload(current), deps.redactEnabled());
|
|
666
708
|
}
|
|
@@ -755,8 +797,9 @@ export class TurnRunner {
|
|
|
755
797
|
* msg_limit=` (renderable rows, limited payload shape, windowed tool calls, todo state) with the full count.
|
|
756
798
|
*/
|
|
757
799
|
terminalSessionPayload(s) {
|
|
758
|
-
const payload = s.compact(
|
|
759
|
-
const
|
|
800
|
+
const payload = withSessionWireFlags(s.compact(), this.registry.liveIds);
|
|
801
|
+
const scened = hydrateAnchorActivityScenes(withToolCallOutcomes(withTurnIds(s.messages), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: true });
|
|
802
|
+
const [window, offset] = messageWindowForDisplay(scened, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
|
|
760
803
|
const limited = messagesForLimitedPayload(window);
|
|
761
804
|
payload.messages = limited;
|
|
762
805
|
payload.message_count = s.messages.length;
|
|
@@ -769,7 +812,7 @@ export class TurnRunner {
|
|
|
769
812
|
/** Python `_materialize_pending_user_turn_before_error` + error message append + save. */
|
|
770
813
|
persistError(s, streamId, label, payload, activeTurnToken) {
|
|
771
814
|
const startedAt = s.pending_started_at;
|
|
772
|
-
this.materializePendingUserTurn(s, activeTurnToken);
|
|
815
|
+
this.materializePendingUserTurn(s, activeTurnToken, streamId);
|
|
773
816
|
const duration = typeof startedAt === 'number' && startedAt > 0 ? Math.max(0, this.deps.now() - startedAt) : null;
|
|
774
817
|
s.active_stream_id = null;
|
|
775
818
|
s.pending_user_message = null;
|
|
@@ -778,9 +821,11 @@ export class TurnRunner {
|
|
|
778
821
|
s.pending_user_source = null;
|
|
779
822
|
this.appendPartialSnapshot(s, streamId);
|
|
780
823
|
const hint = str(payload.hint);
|
|
781
|
-
const errorMessage = { role: 'assistant', content: `**${label}:** ${str(payload.message) || label}${hint ? `\n\n*${hint}*` : ''}`, timestamp: Math.trunc(this.deps.now()), _error: true };
|
|
824
|
+
const errorMessage = { role: 'assistant', content: `**${label}:** ${str(payload.message) || label}${hint ? `\n\n*${hint}*` : ''}`, timestamp: Math.trunc(this.deps.now()), _error: true, _turn_id: streamId };
|
|
782
825
|
if (duration !== null)
|
|
783
826
|
errorMessage._turnDuration = Math.round(duration * 1000) / 1000;
|
|
827
|
+
// The outcome the live frame reported stays on the row, so the settled scene says the same.
|
|
828
|
+
errorMessage._terminal_state = str(payload.terminal_state) || 'error';
|
|
784
829
|
if (payload.type === 'compression_exhausted') {
|
|
785
830
|
// Python `stamp_compression_exhausted_recovery`: durable recovery metadata on the session, the marker, and the frame.
|
|
786
831
|
const recovery = stampCompressionExhaustedRecovery(s, str(payload.message) || label, str(payload.details));
|
|
@@ -795,6 +840,7 @@ export class TurnRunner {
|
|
|
795
840
|
else if (payload.type === 'interrupted')
|
|
796
841
|
errorMessage.provider_details_label = 'Interruption details';
|
|
797
842
|
s.messages.push(errorMessage);
|
|
843
|
+
this.persistConsumedSteers(s, streamId, startedAt, this.deps.now());
|
|
798
844
|
try {
|
|
799
845
|
this.deps.store.save(s);
|
|
800
846
|
}
|
|
@@ -805,7 +851,7 @@ export class TurnRunner {
|
|
|
805
851
|
this.deps.pending.clearClarifies(s.session_id);
|
|
806
852
|
this.deps.events.publish('session_error', { profile: s.profile, sessionId: s.session_id });
|
|
807
853
|
}
|
|
808
|
-
materializePendingUserTurn(s, activeTurnToken) {
|
|
854
|
+
materializePendingUserTurn(s, activeTurnToken, turnId) {
|
|
809
855
|
const pendingText = str(s.pending_user_message);
|
|
810
856
|
if (!pendingText)
|
|
811
857
|
return false;
|
|
@@ -816,7 +862,7 @@ export class TurnRunner {
|
|
|
816
862
|
for (let i = s.messages.length - 1; i >= 0; i -= 1) {
|
|
817
863
|
const m = s.messages[i];
|
|
818
864
|
if (m.role === 'user' && m._active_turn_token === activeTurnToken) {
|
|
819
|
-
if (!s.messages.slice(i + 1).some((later) => later.role === 'user'))
|
|
865
|
+
if (!s.messages.slice(i + 1).some((later) => later.role === 'user' && !later._steer))
|
|
820
866
|
return false;
|
|
821
867
|
break;
|
|
822
868
|
}
|
|
@@ -826,7 +872,7 @@ export class TurnRunner {
|
|
|
826
872
|
// Python `_synthesize_user_message_on_cancel`: a worker that already merged this prompt (same text, not older than the pending start) wins.
|
|
827
873
|
if (last?.role === 'user' && messageText(last.content).trim() === pendingText.trim() && Math.trunc(Number(last.timestamp)) >= Math.trunc(recoveredTs))
|
|
828
874
|
return false;
|
|
829
|
-
const recovered = { role: 'user', content: pendingText, timestamp: recoveredTs, _recovered: true };
|
|
875
|
+
const recovered = { role: 'user', content: pendingText, timestamp: recoveredTs, _recovered: true, _turn_id: turnId };
|
|
830
876
|
if (source !== 'webui')
|
|
831
877
|
recovered._source = source;
|
|
832
878
|
if (attachments.length)
|
|
@@ -840,27 +886,33 @@ export class TurnRunner {
|
|
|
840
886
|
const partial = buildPartialMessage((this.registry.partialText.get(streamId) ?? []).join(''), (this.registry.reasoningText.get(streamId) ?? []).join(''), this.registry.liveToolCalls.get(streamId) ?? [], this.deps.now());
|
|
841
887
|
if (!partial)
|
|
842
888
|
return;
|
|
889
|
+
partial._turn_id = streamId;
|
|
843
890
|
const key = messageIdentity(partial);
|
|
844
891
|
if (key !== null && s.messages.some((m) => m._partial && messageIdentity(m) === key))
|
|
845
892
|
return;
|
|
846
893
|
s.messages.push(partial);
|
|
847
894
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
895
|
+
/**
|
|
896
|
+
* Python `_emit_cancel_event`: the terminal frame carries the settled session so tabs can render the partial without a
|
|
897
|
+
* reload. TAL-364: no copy — clients show their one localized status for the `cancelled` outcome.
|
|
898
|
+
*/
|
|
852
899
|
cancelFrame(sessionId) {
|
|
853
900
|
let snapshot = null;
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
901
|
+
if (sessionId)
|
|
902
|
+
try {
|
|
903
|
+
snapshot = redactSessionData(this.terminalSessionPayload(this.deps.store.get(sessionId)), this.deps.redactEnabled());
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
snapshot = null;
|
|
907
|
+
}
|
|
908
|
+
return { type: 'cancelled', status: 'cancelled', terminal_state: 'cancelled', ...(sessionId ? { session_id: sessionId } : {}), ...(snapshot ? { session: snapshot } : {}) };
|
|
861
909
|
}
|
|
862
|
-
/**
|
|
863
|
-
|
|
910
|
+
/**
|
|
911
|
+
* Python `_finalize_cancelled_turn`/`_persist_cancelled_turn`: only while this stream still owns writeback. TAL-364: the
|
|
912
|
+
* turn's model context keeps what the Agent had captured (`checkpoint`, its canonical transcript) and the prose that
|
|
913
|
+
* streamed, so the next request continues from it; the terminal row carries the outcome and no copy.
|
|
914
|
+
*/
|
|
915
|
+
finalizeCancelled(s, streamId, ephemeral, checkpoint = null) {
|
|
864
916
|
if (this.registry.writebackOwners.get(s.session_id) !== streamId)
|
|
865
917
|
return false;
|
|
866
918
|
let current = s;
|
|
@@ -880,17 +932,44 @@ export class TurnRunner {
|
|
|
880
932
|
this.deps.store.sessions.delete(current.session_id);
|
|
881
933
|
return true;
|
|
882
934
|
}
|
|
883
|
-
|
|
935
|
+
const stop = this.stopContexts.get(streamId);
|
|
936
|
+
const canonical = stop !== undefined && checkpoint !== null && checkpointTurnStart(checkpoint, stop.msgText, stop.historyLength) !== null;
|
|
937
|
+
const settle = () => {
|
|
938
|
+
if (!stop)
|
|
939
|
+
return null;
|
|
940
|
+
if (canonical)
|
|
941
|
+
stop.checkpointed = true;
|
|
942
|
+
return stoppedTurnContext(stop.previousContext, checkpoint, stop.prompt, stop.msgText, (this.registry.partialText.get(streamId) ?? []).join(''), stop.historyLength);
|
|
943
|
+
};
|
|
944
|
+
if (current.messages.some((m) => isCancelMarker(m)) && current.active_stream_id === null && !current.pending_user_message) {
|
|
945
|
+
// The worker's canonical result can arrive after cancel() settled without a checkpoint (the interrupt reply failed
|
|
946
|
+
// or timed out): it replaces this stream's context and never adds a second Stop row. A settlement that already had
|
|
947
|
+
// one keeps it, so work the Agent finished after the Stop stays out.
|
|
948
|
+
const late = canonical && !stop.checkpointed && current.messages.some((m) => isCancelMarker(m) && m._turn_id === streamId) ? settle() : null;
|
|
949
|
+
if (late) {
|
|
950
|
+
current.context_messages = dedupeContext(late);
|
|
951
|
+
try {
|
|
952
|
+
this.deps.store.save(current);
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
884
958
|
return true;
|
|
885
|
-
|
|
959
|
+
}
|
|
960
|
+
const startedAt = current.pending_started_at;
|
|
961
|
+
this.materializePendingUserTurn(current, buildActiveTurnToken(streamId, current.pending_started_at), streamId);
|
|
962
|
+
const context = settle();
|
|
963
|
+
if (context)
|
|
964
|
+
current.context_messages = dedupeContext(context);
|
|
886
965
|
current.active_stream_id = null;
|
|
887
966
|
current.pending_user_message = null;
|
|
888
967
|
current.pending_attachments = [];
|
|
889
968
|
current.pending_started_at = null;
|
|
890
969
|
current.pending_user_source = null;
|
|
891
970
|
this.appendPartialSnapshot(current, streamId);
|
|
892
|
-
|
|
893
|
-
|
|
971
|
+
current.messages.push({ role: 'assistant', content: '', _error: true, _terminal_state: 'cancelled', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
|
|
972
|
+
this.persistConsumedSteers(current, streamId, startedAt, this.deps.now());
|
|
894
973
|
try {
|
|
895
974
|
this.deps.store.save(current);
|
|
896
975
|
}
|
|
@@ -916,7 +995,11 @@ export class TurnRunner {
|
|
|
916
995
|
this.writers.delete(streamId);
|
|
917
996
|
}
|
|
918
997
|
this.abortControllers.delete(streamId);
|
|
998
|
+
this.interrupts.delete(streamId);
|
|
999
|
+
this.stopContexts.delete(streamId);
|
|
919
1000
|
this.steers.delete(streamId);
|
|
1001
|
+
this.consumedSteers.delete(streamId);
|
|
1002
|
+
this.lastCompletedTool.delete(streamId);
|
|
920
1003
|
this.settledStreams.delete(streamId);
|
|
921
1004
|
this.registry.retire(streamId, this.deps.now());
|
|
922
1005
|
this.registry.forgetOwner(streamId);
|
|
@@ -1079,16 +1162,19 @@ export class TurnRunner {
|
|
|
1079
1162
|
}
|
|
1080
1163
|
// Python `_finalize_webui_steers` drained the Agent's pending steer text at cancel time: the interrupt reply
|
|
1081
1164
|
// carries it so queued steers settle as consumed / leftover before the terminal row.
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
leftover = '';
|
|
1090
|
-
}
|
|
1165
|
+
// A duplicate Stop (another tab, a retried request) shares the first interrupt: its snapshot is the boundary, and a
|
|
1166
|
+
// later interrupt finds the run already unwinding.
|
|
1167
|
+
let interrupt = this.interrupts.get(streamId);
|
|
1168
|
+
if (!interrupt) {
|
|
1169
|
+
const sidecar = this.deps.sidecar();
|
|
1170
|
+
interrupt = sidecar ? sidecar.call('chat.interrupt', { stream_id: streamId }, { timeoutMs: 5_000 }).catch(() => null) : Promise.resolve(null);
|
|
1171
|
+
this.interrupts.set(streamId, interrupt);
|
|
1091
1172
|
}
|
|
1173
|
+
const reply = await interrupt;
|
|
1174
|
+
const leftover = str(reply?.pending_steer);
|
|
1175
|
+
const checkpoint = reply?.checkpoint ?? null;
|
|
1176
|
+
// Settle the steers first so the persisted cancel carries every consumed one.
|
|
1177
|
+
const steerEvents = (this.steers.get(streamId) ?? []).length ? this.finalizeSteerEvents(streamId, leftover) : [];
|
|
1092
1178
|
if (sessionId) {
|
|
1093
1179
|
let current = null;
|
|
1094
1180
|
try {
|
|
@@ -1098,7 +1184,7 @@ export class TurnRunner {
|
|
|
1098
1184
|
current = null;
|
|
1099
1185
|
}
|
|
1100
1186
|
if (current?.active_stream_id === streamId) {
|
|
1101
|
-
this.finalizeCancelled(current, streamId,
|
|
1187
|
+
this.finalizeCancelled(current, streamId, run?.ephemeral, checkpoint);
|
|
1102
1188
|
}
|
|
1103
1189
|
}
|
|
1104
1190
|
// The worker may already have written its terminal row while the interrupt was in flight; the stream is then
|
|
@@ -1117,10 +1203,9 @@ export class TurnRunner {
|
|
|
1117
1203
|
}
|
|
1118
1204
|
channel.put([event, data, eventId]);
|
|
1119
1205
|
};
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
emit('cancel', sessionId ? this.cancelFrame(sessionId) : this.cancelPayload());
|
|
1206
|
+
for (const [event, data] of steerEvents)
|
|
1207
|
+
emit(event, data);
|
|
1208
|
+
emit('cancel', this.cancelFrame(sessionId));
|
|
1124
1209
|
this.registry.streams.delete(streamId);
|
|
1125
1210
|
this.registry.liveIds.delete(streamId);
|
|
1126
1211
|
}
|
|
@@ -1196,7 +1281,70 @@ export class TurnRunner {
|
|
|
1196
1281
|
return null;
|
|
1197
1282
|
}
|
|
1198
1283
|
consumedSteerPayload(record) {
|
|
1199
|
-
|
|
1284
|
+
const consumedAt = this.deps.now();
|
|
1285
|
+
const afterToolCallId = this.lastCompletedTool.get(record.stream_id) ?? null;
|
|
1286
|
+
const consumed = this.consumedSteers.get(record.stream_id) ?? [];
|
|
1287
|
+
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 });
|
|
1289
|
+
this.consumedSteers.set(record.stream_id, consumed);
|
|
1290
|
+
}
|
|
1291
|
+
return { ...record, agent_text: record.text, text: record.display_text || record.text, consumed_at: consumedAt, after_tool_call_id: afterToolCallId };
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Consumed steers become display-only `_steer` user rows at their causal place in the turn: after the tool results of
|
|
1295
|
+
* the call that had completed when the Agent took them (else right after the turn's prompt), in consumption order,
|
|
1296
|
+
* each with the phase it ended. Returns where the last phase starts. Replaces the turn's earlier placement, so the
|
|
1297
|
+
* mid-turn save and the terminal one agree. Leftover steers are not persisted.
|
|
1298
|
+
*/
|
|
1299
|
+
placeConsumedSteers(s, streamId, startedAt) {
|
|
1300
|
+
const steers = this.consumedSteers.get(streamId) ?? [];
|
|
1301
|
+
if (!steers.length)
|
|
1302
|
+
return null;
|
|
1303
|
+
s.messages = s.messages.filter((m) => !(m._steer && m._turn_id === streamId));
|
|
1304
|
+
let boundary = typeof startedAt === 'number' && startedAt > 0 ? startedAt : steers[0].submitted_at;
|
|
1305
|
+
for (const steer of steers) {
|
|
1306
|
+
const row = {
|
|
1307
|
+
role: 'user', content: steer.text, timestamp: steer.consumed_at, _turn_id: streamId,
|
|
1308
|
+
_steer: { steer_id: steer.steer_id, submitted_at: steer.submitted_at, consumed_at: steer.consumed_at, phase_duration: roundDuration(steer.consumed_at - boundary) },
|
|
1309
|
+
};
|
|
1310
|
+
// An Agent that records the steer it delivered does so at its exact place: that row becomes the steer, not a copy.
|
|
1311
|
+
const agentRows = s.messages.flatMap((m, i) => (m._turn_id === streamId && agentSteerText(m) !== null ? [i] : []));
|
|
1312
|
+
const agentRow = agentRows.find((i) => agentSteerText(s.messages[i]) === steer.agent_text.trim()) ?? agentRows[0];
|
|
1313
|
+
if (agentRow === undefined)
|
|
1314
|
+
s.messages.splice(steerInsertIndex(s.messages, streamId, steer.after_tool_call_id), 0, row);
|
|
1315
|
+
else
|
|
1316
|
+
s.messages[agentRow] = row;
|
|
1317
|
+
boundary = steer.consumed_at;
|
|
1318
|
+
}
|
|
1319
|
+
return boundary;
|
|
1320
|
+
}
|
|
1321
|
+
/** A steer is saved as it enters the stream, so a reload mid-turn already has it; the terminal save re-places it. */
|
|
1322
|
+
saveConsumedSteers(sessionId, streamId) {
|
|
1323
|
+
let s;
|
|
1324
|
+
try {
|
|
1325
|
+
s = this.deps.store.get(sessionId);
|
|
1326
|
+
}
|
|
1327
|
+
catch {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (s.active_stream_id !== streamId || this.placeConsumedSteers(s, streamId, s.pending_started_at) === null)
|
|
1331
|
+
return;
|
|
1332
|
+
try {
|
|
1333
|
+
this.deps.store.save(s, { touchUpdatedAt: false });
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
this.deps.log(`[webui] WARNING: failed to save consumed steer for ${sessionId}: ${error.message}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
/** Terminal placement: the turn's rows are final now, and its last reply carries the final phase. */
|
|
1340
|
+
persistConsumedSteers(s, streamId, startedAt, endedAt) {
|
|
1341
|
+
const boundary = this.placeConsumedSteers(s, streamId, startedAt);
|
|
1342
|
+
this.consumedSteers.delete(streamId);
|
|
1343
|
+
if (boundary === null)
|
|
1344
|
+
return;
|
|
1345
|
+
const last = s.messages.findLast((m) => m.role === 'assistant' && m._turn_id === streamId);
|
|
1346
|
+
if (last)
|
|
1347
|
+
last._final_phase_duration = roundDuration(endedAt - boundary);
|
|
1200
1348
|
}
|
|
1201
1349
|
/**
|
|
1202
1350
|
* Python `_take_consumed_webui_steers` (live, `keepLeftovers`) and `_finalize_webui_steers` (terminal): records the
|
|
@@ -1315,11 +1463,18 @@ export class TurnRunner {
|
|
|
1315
1463
|
return this.deps.service().setYolo(sessionId, false);
|
|
1316
1464
|
});
|
|
1317
1465
|
}
|
|
1318
|
-
/**
|
|
1319
|
-
|
|
1466
|
+
/**
|
|
1467
|
+
* `reply` is the raw Agent reply or keyed step answers the server shapes for the prompt. `stale` when no such
|
|
1468
|
+
* prompt is queued; `invalid` when the answers do not fit its steps; `ok:false` without either when the sidecar
|
|
1469
|
+
* did not acknowledge (prompt retained).
|
|
1470
|
+
*/
|
|
1471
|
+
async respondClarify(sessionId, clarifyId, reply) {
|
|
1320
1472
|
const entry = this.deps.pending.peekClarify(sessionId, clarifyId);
|
|
1321
1473
|
if (!entry)
|
|
1322
1474
|
return { ok: false, stale: true };
|
|
1475
|
+
const response = typeof reply === 'string' ? reply : clarifyReply(entry, reply);
|
|
1476
|
+
if (response === null)
|
|
1477
|
+
return { ok: false, invalid: true };
|
|
1323
1478
|
const sidecar = this.deps.sidecar();
|
|
1324
1479
|
if (!sidecar)
|
|
1325
1480
|
return { ok: false, error: 'The Agent sidecar is not running; retry in a moment.' };
|
|
@@ -1335,12 +1490,16 @@ export class TurnRunner {
|
|
|
1335
1490
|
const { head } = this.deps.pending.resolveClarify(sessionId, str(entry.clarify_id));
|
|
1336
1491
|
if (head)
|
|
1337
1492
|
this.emitToSession(sessionId, 'clarify', head);
|
|
1338
|
-
return { ok: true };
|
|
1493
|
+
return { ok: true, response };
|
|
1339
1494
|
}
|
|
1340
1495
|
}
|
|
1341
1496
|
function previousStartedAt(s, run) {
|
|
1342
1497
|
return typeof s.pending_started_at === 'number' && s.pending_started_at > 0 ? s.pending_started_at : (run?.started_at ?? Date.now() / 1000);
|
|
1343
1498
|
}
|
|
1499
|
+
/** A persisted Stop row: TAL-364 marks its outcome; older rows carry only their English copy. */
|
|
1500
|
+
function isCancelMarker(m) {
|
|
1501
|
+
return m._error === true && (m._terminal_state === 'cancelled' || str(m.content).startsWith('**Task cancelled:**'));
|
|
1502
|
+
}
|
|
1344
1503
|
function dedupeContext(messages) {
|
|
1345
1504
|
const out = [];
|
|
1346
1505
|
let lastKey = null;
|
|
@@ -1354,4 +1513,35 @@ function dedupeContext(messages) {
|
|
|
1354
1513
|
return out;
|
|
1355
1514
|
}
|
|
1356
1515
|
export { HttpFailure };
|
|
1516
|
+
/** Where a consumed steer goes: past the tool results of the call it followed, else past the turn's prompt. */
|
|
1517
|
+
function roundDuration(seconds) {
|
|
1518
|
+
return Math.round(Math.max(0, seconds) * 1000) / 1000;
|
|
1519
|
+
}
|
|
1520
|
+
function steerInsertIndex(messages, turnId, afterToolCallId) {
|
|
1521
|
+
// OpenAI-style `tool_calls` and Anthropic-style `tool_use` content blocks both name the call a steer followed.
|
|
1522
|
+
const callIds = (m) => [
|
|
1523
|
+
...(Array.isArray(m.tool_calls) ? m.tool_calls : []).map((tc) => str(tc.id) || str(tc.call_id) || str(tc.tool_call_id)),
|
|
1524
|
+
...(Array.isArray(m.content) ? m.content : []).flatMap((part) => (isDict(part) && part.type === 'tool_use' ? [str(part.id)] : [])),
|
|
1525
|
+
];
|
|
1526
|
+
// The steer followed that call's result: right after its row (a `tool` row, or a user row of `tool_result` blocks), even
|
|
1527
|
+
// when the same assistant message made later calls; steers already placed there keep their consumption order.
|
|
1528
|
+
const answers = (m) => (m.role === 'tool' && (str(m.tool_call_id) || str(m.tool_use_id)) === afterToolCallId)
|
|
1529
|
+
|| (m.role === 'user' && Array.isArray(m.content) && m.content.some((part) => isDict(part) && part.type === 'tool_result' && str(part.tool_use_id) === afterToolCallId));
|
|
1530
|
+
const result = afterToolCallId ? messages.findIndex((m) => m._turn_id === turnId && answers(m)) : -1;
|
|
1531
|
+
if (result !== -1) {
|
|
1532
|
+
let i = result + 1;
|
|
1533
|
+
while (i < messages.length && messages[i]._turn_id === turnId && messages[i]._steer)
|
|
1534
|
+
i += 1;
|
|
1535
|
+
return i;
|
|
1536
|
+
}
|
|
1537
|
+
let at = afterToolCallId ? messages.findIndex((m) => m._turn_id === turnId && m.role === 'assistant' && callIds(m).includes(afterToolCallId)) : -1;
|
|
1538
|
+
if (at === -1)
|
|
1539
|
+
at = messages.findIndex((m) => m._turn_id === turnId && m.role === 'user' && !m._steer);
|
|
1540
|
+
if (at === -1)
|
|
1541
|
+
return messages.length;
|
|
1542
|
+
let i = at + 1;
|
|
1543
|
+
while (i < messages.length && messages[i]._turn_id === turnId && (messages[i].role === 'tool' || messages[i]._steer))
|
|
1544
|
+
i += 1;
|
|
1545
|
+
return i;
|
|
1546
|
+
}
|
|
1357
1547
|
//# sourceMappingURL=turn.js.map
|