@maudecode/talaria-web 1.11.0 → 1.13.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/LICENSE +22 -0
- package/_release.json +4 -4
- package/dist/api/chat-router.d.ts +68 -4
- 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 +407 -51
- package/dist/api/sessions-router.js +2 -2
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/sse-routes.js +52 -8
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +443 -0
- package/dist/api/tools-router.js +99 -4
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +926 -55
- package/dist/auth/oidc.js +2 -0
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/passkeys.js +1 -1
- package/dist/auth/passkeys.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/cli/ctl.d.ts +6 -3
- package/dist/cli/ctl.js +25 -16
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/launcher.js +2 -2
- package/dist/cli/launcher.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/mcp/server.js +1 -1
- package/dist/mcp/server.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 +20 -9
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +12 -6
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +2 -0
- package/dist/sessions/anchor.js +22 -17
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/export.js +1 -1
- package/dist/sessions/export.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 +66 -2
- package/dist/sessions/merge.js +291 -9
- 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/relay.js +1 -1
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +16 -3
- package/dist/sessions/service.js +92 -54
- package/dist/sessions/service.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 +25 -7
- package/dist/sessions/turn.js +153 -90
- package/dist/sessions/turn.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/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 +133 -0
- package/dist/tools/update-notifications.js +407 -0
- package/dist/tools/update-notifications.js.map +1 -0
- package/dist/tools/updates.d.ts +53 -16
- package/dist/tools/updates.js +314 -351
- package/dist/tools/updates.js.map +1 -1
- package/package.json +3 -2
- package/sidecar/scripts/replay_sidecar.py +17 -0
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +39 -16
- package/sidecar/talaria_sidecar/methods/chat.py +113 -4
- package/static/dist/FILES.txt +116 -116
- package/static/dist/THIRD_PARTY_LICENSES.md +5170 -0
- package/static/dist/assets/AppShell-irdqom6e.js +1 -0
- package/static/dist/assets/ChatPage-ldhqevjn.js +20 -0
- package/static/dist/assets/HubRoute-hq24vhj8.js +2 -0
- package/static/dist/assets/{Markdown-ftyfzds0.js → Markdown-bx4n7qs4.js} +7 -7
- package/static/dist/assets/{RightPanel-febk3dkd.js → RightPanel-ds4majj3.js} +1 -1
- package/static/dist/assets/Select-gpqmt6x1.js +1 -0
- package/static/dist/assets/SessionListPanel-hkvipik4.js +1 -0
- package/static/dist/assets/{SettingsLayout-pdpmhy1o.js → SettingsLayout-oigbg9rl.js} +1 -1
- package/static/dist/assets/States-fn4o2wdf.js +1 -0
- package/static/dist/assets/Toaster-cwnut53k.js +1 -0
- package/static/dist/assets/_app-h6ago6y6.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-erqnfff5.js +1 -0
- package/static/dist/assets/_app.index-cq2fxmda.js +1 -0
- package/static/dist/assets/_app.insights-nzhld9gu.js +1 -0
- package/static/dist/assets/_app.kanban-m5uggety.js +1 -0
- package/static/dist/assets/_app.logs-d2kk9lza.js +1 -0
- package/static/dist/assets/_app.memory-gcmop1e7.js +1 -0
- package/static/dist/assets/_app.profiles-e9b2my7x.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-n1ggcf5e.js → _app.session._sessionId-ciix6itb.js} +1 -1
- package/static/dist/assets/_app.settings-kcjum3c1.js +1 -0
- package/static/dist/assets/_app.settings._section-bi4jjex0.js +1 -0
- package/static/dist/assets/_app.skills-e4e61l23.js +1 -0
- package/static/dist/assets/_app.tasks-f1evdhbl.js +2 -0
- package/static/dist/assets/_app.todos-o7ijtk42.js +1 -0
- package/static/dist/assets/_app.workspaces-o18voh0w.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-i4bgzu83.js → abnfDiagram-VCTEODGH-krt0yaru.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-notdhmpv.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-mx21lwqa.js → architectureDiagram-5GKGNRK7-i54t9ywl.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-o1hhchxv.js → blockDiagram-I7D4REHJ-fevbz4mz.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-jygqtvd8.js → c4Diagram-7LVT6UL2-np8eclmc.js} +1 -1
- package/static/dist/assets/channel-jpk1nas2.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-hjcpubgk.js → chunk-4HAMMTFA-owoi036m.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-eibnex6m.js → chunk-75Z2AOVW-md44zf1n.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-k1tp3qnp.js → chunk-DU6HZSFF-ka3rw8d6.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-lvcgn16f.js → chunk-F27PBJKO-iasnjsky.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ldlzruv1.js → chunk-GMAD6QVW-ex1up6tz.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-hs1mva9d.js → chunk-GVQU2GXP-d4kmmg9p.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-o7eqx3yb.js → chunk-IMKFNOWR-mwy42t85.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-bp1ofl70.js → chunk-L3NEJ4N5-fv7ry213.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-o8m6mdz5.js → chunk-OSK3NFVY-jg9ir8ac.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-kx83fti2.js → chunk-P2QGCYS3-pe9ms3ua.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-mwl99e9y.js → chunk-POPQ4Y6H-dimsjrn1.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-oriuokxy.js → chunk-PWAF6VOD-m4hv98dy.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-ab5cf00v.js → chunk-SHT3W25Y-gkjgl6qe.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-kqdrpr17.js → chunk-SVP7TREG-bc8qss5l.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-kfbnjabk.js → chunk-TICWLB2K-k9wzw38w.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-hld301gc.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-hld301gc.js +1 -0
- package/static/dist/assets/{copied-dp33szhq.js → copied-d5joolw7.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-mm5sj9s7.js → cynefin-OW5HDTMX-hueklikk.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-nfon5q38.js → cynefinDiagram-5FMLGOSQ-e83o1wr1.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-dzdp9ypb.js → dagre-GXQ25YYZ-ir1tj2ar.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-ckxzqo01.js → diagram-S7CK7UJ4-o5218p2q.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-hw16h1um.js → diagram-UQ7AKVKN-ciad4o1i.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-mu7wu2zg.js → diagram-VSXAHHWV-oharqjbt.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-k9uprwtj.js → diagram-VX7I27RA-wkwy62iv.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-csdjzrg9.js → diagram-Z3DM3KII-bl252i7r.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-n8mbw2jj.js → ebnfDiagram-PWID7BFC-nm0rcly7.js} +1 -1
- package/static/dist/assets/endpoints-d8n28yf3.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-fsfe1uj0.js → erDiagram-RLTQ6QDP-m1w0vta2.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-eaqs19mj.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-eli5764n.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-gsba3iq9.js → ganttDiagram-EL5Y4UJY-czxo6iew.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-na86q1pu.js → gitGraph-4MIJSDKK-ip9j1rph.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-g72ew3xz.js → gitGraphDiagram-WWUBYQGX-kalxdole.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-okn18cp1.js → highlighted-body-KPVGNVTW-lgsj6fsk.js} +1 -1
- package/static/dist/assets/index-l08181vr.js +11 -0
- package/static/dist/assets/index-pdtcl20t.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-i01jeltk.js → info-A6RAGUB7-fv4fbc5m.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-cayiv6fa.js → infoDiagram-27XIBGKW-kinx5jsl.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-g4rf14bv.js → ishikawaDiagram-5VMMS53U-kozvlqdv.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-fusb2424.js → journeyDiagram-3NMN7TZE-lgpouxoe.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-g831pwlr.js → kanban-definition-UXKFOSKX-b320wo7b.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-ol965zwk.js → lazyRouteComponent-hy8doeye.js} +1 -1
- package/static/dist/assets/{line-md6em6gc.js → line-gxfwyhjq.js} +1 -1
- package/static/dist/assets/{login-kfclsqd2.js → login-llplsuey.js} +1 -1
- package/static/dist/assets/mermaid-HWGCJPDP-f3scrlds.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-mq3uns9m.js → mermaid-parser.core-loxeqsu3.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-oxafrned.js → mindmap-definition-YA3MSWOX-edi534gc.js} +1 -1
- package/static/dist/assets/{onboarding-fo6hnkyv.js → onboarding-n1xhlgym.js} +1 -1
- package/static/dist/assets/{packet-AYTQ26CC-ggxyvbym.js → packet-AYTQ26CC-gmb3yy71.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-nk7xcqgp.js → pegDiagram-XKGWAZYB-fxy5j8cx.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-ito2razh.js → pie-WAS4IAKB-do2pm4s6.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-g02xexar.js → pieDiagram-E7YTZNPT-mgjvb365.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-d4jn4xq5.js → quadrantDiagram-AXDQQJYC-dhok6m1j.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-nebvfdzt.js → radar-RG4KPBEZ-n5giw3vb.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-ny3gebse.js → railroad-74A4TZTK-h6nfs9bj.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-d5gaaud9.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ihudnbec.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-lgpvhel7.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-m09mmu67.js → railroadDiagram-O6MQD6OU-lfwg92r4.js} +1 -1
- package/static/dist/assets/{refresh-cw-eitd7yva.js → refresh-cw-bg6oc499.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-evggpcbk.js → requirementDiagram-BXWQKSXE-c5k5pg07.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-bun89039.js → sankeyDiagram-P5KCCOFB-o9auio4y.js} +1 -1
- package/static/dist/assets/{saved-culx14ii.js → saved-j5wyhbik.js} +1 -1
- package/static/dist/assets/{search-nu6kzq55.js → search-ipa17zlr.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-hhs9f09o.js → sequenceDiagram-WJ2MYXX4-pfgodnmk.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-mj0gcozh.js → settings_section_extensions_title-fcfjf6l8.js} +1 -1
- package/static/dist/assets/share._token-i339gqj4.js +2 -0
- package/static/dist/assets/{sliders-horizontal-b9ljga1o.js → sliders-horizontal-bfrh4rch.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-gi7t1tro.js → stateDiagram-D77RDMKH-m1y2cr3q.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hehvfv55.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-ea5urvph.js → swimlanes-42K2YHIH-hzai6fwo.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-nfk9mtst.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-mq64rm3b.js → timeline-definition-24CTP7MA-gwefy9gi.js} +1 -1
- package/static/dist/assets/todoStore-fhvdsen0.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-cimbtxlz.js → treeView-Q6P3EWNA-7q91p0dn.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-wig5gi1y.js → treemap-WGGIJYW6-occom39a.js} +1 -1
- package/static/dist/assets/{untitled-mbwemvxf.js → untitled-cgw2enra.js} +1 -1
- package/static/dist/assets/{useForm-li4bljlv.js → useForm-jwbfla68.js} +1 -1
- package/static/dist/assets/useLocale-gieo1fn7.js +1 -0
- package/static/dist/assets/{vennDiagram-4TSXK5OY-ez63kh7n.js → vennDiagram-4TSXK5OY-ew4bhldi.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-r7rftz31.js → wardley-WFR3VGLG-njdacy0u.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-mvqg9eqc.js → wardleyDiagram-VM6X3IG4-mkvxx7u9.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-eqxdfc91.js → xychartDiagram-S5SC5T6Z-8uriotdw.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/manifest.webmanifest +1 -1
- package/static/dist/sw.js +114 -152
- package/static/dist/assets/AppShell-hikzkcql.js +0 -1
- package/static/dist/assets/ChatPage-ofqn89h9.js +0 -21
- package/static/dist/assets/HubRoute-f2zrs9si.js +0 -2
- package/static/dist/assets/Select-lvx8gihl.js +0 -1
- package/static/dist/assets/SessionListPanel-o70ljmik.js +0 -1
- package/static/dist/assets/States-i46tz8an.js +0 -1
- package/static/dist/assets/Toaster-fklyeeyh.js +0 -1
- package/static/dist/assets/_app-j3rors25.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-c0ey5ec9.js +0 -1
- package/static/dist/assets/_app.index-coy9jrto.js +0 -1
- package/static/dist/assets/_app.insights-ebd2k0i5.js +0 -1
- package/static/dist/assets/_app.kanban-e2p7ypi5.js +0 -1
- package/static/dist/assets/_app.logs-ml21mfyz.js +0 -1
- package/static/dist/assets/_app.memory-lhu5mm47.js +0 -1
- package/static/dist/assets/_app.profiles-egx0fgjo.js +0 -1
- package/static/dist/assets/_app.settings-d0vqtyid.js +0 -1
- package/static/dist/assets/_app.settings._section-cnzj6pli.js +0 -1
- package/static/dist/assets/_app.skills-kkug8obu.js +0 -1
- package/static/dist/assets/_app.tasks-nf285d2o.js +0 -2
- package/static/dist/assets/_app.todos-dxalqp1e.js +0 -1
- package/static/dist/assets/_app.workspaces-bqtduujs.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-o8zqevhv.js +0 -1
- package/static/dist/assets/channel-mw8l793e.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-egajr1wp.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-egajr1wp.js +0 -1
- package/static/dist/assets/endpoints-f6ngsyu5.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-k3zhqcyl.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-bdxes51f.js +0 -1
- package/static/dist/assets/index-ab0wj9ik.css +0 -2
- package/static/dist/assets/index-fd27xooo.js +0 -11
- package/static/dist/assets/mermaid-HWGCJPDP-ejk0d88t.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-gtd38edw.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-o238oax2.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-c4acqp2j.js +0 -1
- package/static/dist/assets/share._token-k4pcnxqu.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-jtadbol7.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ki7weepw.js +0 -8
- package/static/dist/assets/todoStore-azvfuqbv.js +0 -1
- package/static/dist/assets/trash-osyo895g.js +0 -1
- package/static/dist/assets/useLocale-kdew6auf.js +0 -1
package/dist/sessions/turn.js
CHANGED
|
@@ -12,18 +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 {
|
|
23
|
+
import { withSessionWireFlags } from './list.js';
|
|
24
|
+
import { hydrateAnchorActivityScenes, turnTerminalState, withTurnIds } from './anchor.js';
|
|
24
25
|
import { persistentStateChanges, persistentStateSnapshot } from './state-saved.js';
|
|
25
26
|
import { maxIterationsFromConfig, maxTokensFromConfig, processWakeupMaxIterations, reasoningConfigFromConfig, webuiEphemeralSystemPrompt, workspaceSystemMessage } from './turn-context.js';
|
|
26
|
-
import { agentSteerText, 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';
|
|
27
28
|
import { fallbackTitleFromExchange, firstExchangeSnippets, isGenericFallbackTitle, latestExchangeSnippets, looksInvalidGeneratedTitle, sanitizeGeneratedTitle, titleLanguageMismatch, titlePrompts } from './titles.js';
|
|
28
29
|
import { str } from '../util.js';
|
|
29
30
|
export const CHAT_LOCK_WAIT_SECONDS = 2;
|
|
@@ -35,7 +36,7 @@ export function classifyProviderError(errStr, opts = {}) {
|
|
|
35
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.' };
|
|
36
37
|
const cancelled = ['cancelled by user', 'canceled by user', 'user cancelled', 'user canceled', 'task cancelled', 'task canceled', 'cancellederror'].some((k) => lower.includes(k));
|
|
37
38
|
if (cancelled)
|
|
38
|
-
return { label: 'Task cancelled', type: 'cancelled', hint:
|
|
39
|
+
return { label: 'Task cancelled', type: 'cancelled', hint: '' };
|
|
39
40
|
if (['interrupted by user', 'response interrupted', 'operation interrupted', 'operation was interrupted', 'operation aborted', 'request was aborted', 'aborterror'].some((k) => lower.includes(k))) {
|
|
40
41
|
return { label: 'Response interrupted', type: 'interrupted', hint: 'The run stopped before a provider response completed. If you did not cancel it, try again.' };
|
|
41
42
|
}
|
|
@@ -61,12 +62,11 @@ export function classifyProviderError(errStr, opts = {}) {
|
|
|
61
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.' };
|
|
62
63
|
return { label: 'Error', type: 'error', hint: '' };
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
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']);
|
|
67
67
|
export function providerErrorPayload(message, errType, hint = '', redact = true) {
|
|
68
68
|
const safe = redact ? redactString(message).trim() : message;
|
|
69
|
-
const payload = { message: safe || message, type: errType };
|
|
69
|
+
const payload = { message: safe || message, type: errType, terminal_state: CLASSIFIED_OUTCOMES.has(errType) ? errType : 'error' };
|
|
70
70
|
if (hint)
|
|
71
71
|
payload.hint = hint;
|
|
72
72
|
if (safe)
|
|
@@ -139,8 +139,6 @@ export function explicitTextSignal(cfg) {
|
|
|
139
139
|
return !['', 'auto'].includes(provider) || Boolean(str(vision.model).trim()) || Boolean(str(vision.base_url).trim());
|
|
140
140
|
}
|
|
141
141
|
export const GATEWAY_APPROVAL_RELAY_UNAVAILABLE = 'Gateway approval could not be relayed because the active run is unavailable. Reopen the session or retry after it reconnects.';
|
|
142
|
-
/** Error classifications that are turn outcomes in their own right (not a generic failure). */
|
|
143
|
-
const CLASSIFIED_OUTCOMES = new Set(['no_response', 'compression_exhausted', 'interrupted', 'cancelled']);
|
|
144
142
|
export class TurnRunner {
|
|
145
143
|
deps;
|
|
146
144
|
writers = new Map();
|
|
@@ -153,6 +151,13 @@ export class TurnRunner {
|
|
|
153
151
|
lastCompletedTool = new Map();
|
|
154
152
|
/** Streams whose run completed (`done` emitted) and only await title work; a late cancel is a no-op for these. */
|
|
155
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();
|
|
156
161
|
constructor(deps) {
|
|
157
162
|
this.deps = deps;
|
|
158
163
|
}
|
|
@@ -225,17 +230,7 @@ export class TurnRunner {
|
|
|
225
230
|
const latest = s.messages[s.messages.length - 1];
|
|
226
231
|
if (latest?.role === 'user' && messageText(latest.content).split(/\s+/).join(' ') === msg.split(/\s+/).join(' '))
|
|
227
232
|
return;
|
|
228
|
-
|
|
229
|
-
const token = buildActiveTurnToken(s.active_stream_id, startedAt);
|
|
230
|
-
if (token)
|
|
231
|
-
user._active_turn_token = token;
|
|
232
|
-
if (source !== 'webui')
|
|
233
|
-
user._source = source;
|
|
234
|
-
if (typeof startedAt === 'number' && startedAt > 0)
|
|
235
|
-
user.timestamp = startedAt;
|
|
236
|
-
if (attachments.length)
|
|
237
|
-
user.attachments = [...attachments];
|
|
238
|
-
s.messages.push(user);
|
|
233
|
+
s.messages.push(pendingUserRow(msg, attachments, startedAt, source, turnId));
|
|
239
234
|
}
|
|
240
235
|
// ── worker ───────────────────────────────────────────────────────────────
|
|
241
236
|
async run(sessionId, streamId, channel, opts) {
|
|
@@ -248,7 +243,7 @@ export class TurnRunner {
|
|
|
248
243
|
catch (error) {
|
|
249
244
|
deps.log(`[webui] WARNING: run journal degraded for stream ${streamId}: ${error.message}`);
|
|
250
245
|
}
|
|
251
|
-
const put = (event, data) => {
|
|
246
|
+
const put = (event, data, meta = {}) => {
|
|
252
247
|
for (const steerEvent of this.takeSteerEventsBefore(streamId, event))
|
|
253
248
|
put(steerEvent[0], steerEvent[1]);
|
|
254
249
|
if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', 'pending_steer_leftover'].includes(event))
|
|
@@ -264,16 +259,20 @@ export class TurnRunner {
|
|
|
264
259
|
let eventId = null;
|
|
265
260
|
if (writer) {
|
|
266
261
|
try {
|
|
267
|
-
eventId = writer.appendSseEvent(event, data).event_id;
|
|
262
|
+
eventId = writer.appendSseEvent(event, data, meta).event_id;
|
|
268
263
|
}
|
|
269
264
|
catch (error) {
|
|
265
|
+
this.registry.degradedJournals.add(streamId);
|
|
270
266
|
deps.log(`[webui] WARNING: run journal append failed for ${streamId}/${event}: ${error.message}`);
|
|
271
267
|
}
|
|
272
268
|
}
|
|
273
|
-
channel.put([event, data, eventId]);
|
|
269
|
+
channel.put([event, data, eventId, meta.redacted]);
|
|
274
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';
|
|
275
274
|
try {
|
|
276
|
-
deps.onTerminal?.(streamId,
|
|
275
|
+
deps.onTerminal?.(streamId, phase);
|
|
277
276
|
}
|
|
278
277
|
catch { /* best effort */ }
|
|
279
278
|
}
|
|
@@ -285,7 +284,7 @@ export class TurnRunner {
|
|
|
285
284
|
s = deps.store.get(sessionId);
|
|
286
285
|
}
|
|
287
286
|
catch {
|
|
288
|
-
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 });
|
|
289
288
|
this.teardown(sessionId, streamId);
|
|
290
289
|
return;
|
|
291
290
|
}
|
|
@@ -298,19 +297,32 @@ export class TurnRunner {
|
|
|
298
297
|
const previousContext = structuredClone(localContext.some((m) => isContextCompressionMarker(m)) ? localContext : deps.service().mergedTranscript(s, localContext));
|
|
299
298
|
// Python `_sanitize_messages_for_api`: the model never sees display-only rows or a replayed cancelled prompt.
|
|
300
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 });
|
|
301
304
|
const activeTurnToken = buildActiveTurnToken(streamId, s.pending_started_at);
|
|
302
305
|
const sidecar = deps.sidecar();
|
|
303
306
|
const partialText = this.registry.partialText.get(streamId) ?? [];
|
|
304
307
|
const reasoningText = this.registry.reasoningText.get(streamId) ?? [];
|
|
305
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();
|
|
306
315
|
let tokenSent = false;
|
|
307
316
|
let firstTokenAt = null;
|
|
308
317
|
const titles = new ReasoningTitleTracker();
|
|
309
318
|
let capturedTerminalError = null;
|
|
310
319
|
const controller = new AbortController();
|
|
311
320
|
this.abortControllers.set(streamId, controller);
|
|
312
|
-
const workspaceCtx = workspaceContextPrefix(opts.workspace);
|
|
313
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;
|
|
314
326
|
if (activeRun)
|
|
315
327
|
activeRun.phase = 'running';
|
|
316
328
|
const settledAt = { value: false };
|
|
@@ -330,8 +342,8 @@ export class TurnRunner {
|
|
|
330
342
|
// Last yield before `chat.start`: a cancel that landed during the attachment, config or YOLO awaits must not
|
|
331
343
|
// start an Agent turn (the client also refuses an already-aborted signal outright).
|
|
332
344
|
if (this.registry.cancelled.has(streamId)) {
|
|
333
|
-
this.finalizeCancelled(s, streamId,
|
|
334
|
-
put('cancel', this.
|
|
345
|
+
this.finalizeCancelled(s, streamId, opts.ephemeral);
|
|
346
|
+
put('cancel', this.cancelFrame(sessionId));
|
|
335
347
|
return;
|
|
336
348
|
}
|
|
337
349
|
const frozenWorkspace = str(s.created_workspace) || str(s.workspace);
|
|
@@ -379,25 +391,32 @@ export class TurnRunner {
|
|
|
379
391
|
put('steer_consumed', record);
|
|
380
392
|
this.saveConsumedSteers(sessionId, streamId);
|
|
381
393
|
return;
|
|
382
|
-
case 'tool':
|
|
383
|
-
|
|
384
|
-
|
|
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 });
|
|
385
401
|
return;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 });
|
|
396
413
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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 });
|
|
400
418
|
return;
|
|
419
|
+
}
|
|
401
420
|
// Python: the live chat frame carries the queue head plus depth, not the entry that just arrived.
|
|
402
421
|
case 'approval': {
|
|
403
422
|
deps.pending.submitApproval(sessionId, { ...data, session_id: sessionId });
|
|
@@ -429,7 +448,10 @@ export class TurnRunner {
|
|
|
429
448
|
});
|
|
430
449
|
settledAt.value = true;
|
|
431
450
|
if (this.registry.cancelled.has(streamId) || result.status === 'cancelled') {
|
|
432
|
-
|
|
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);
|
|
433
455
|
put('cancel', this.cancelFrame(sessionId));
|
|
434
456
|
return;
|
|
435
457
|
}
|
|
@@ -444,7 +466,7 @@ export class TurnRunner {
|
|
|
444
466
|
}
|
|
445
467
|
opts.onDone?.(answer);
|
|
446
468
|
// Python `_ephemeral_session_payload`: only role and content leave the server for a btw turn.
|
|
447
|
-
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' });
|
|
448
470
|
try {
|
|
449
471
|
rmSync(deps.store.pathFor(sessionId), { force: true });
|
|
450
472
|
}
|
|
@@ -532,7 +554,7 @@ export class TurnRunner {
|
|
|
532
554
|
s.threshold_tokens = result.context.threshold_tokens;
|
|
533
555
|
if (typeof result.context.last_prompt_tokens === 'number')
|
|
534
556
|
s.last_prompt_tokens = result.context.last_prompt_tokens;
|
|
535
|
-
s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls);
|
|
557
|
+
s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls, s.tool_calls);
|
|
536
558
|
s.active_stream_id = null;
|
|
537
559
|
s.pending_user_message = null;
|
|
538
560
|
s.pending_attachments = [];
|
|
@@ -540,14 +562,13 @@ export class TurnRunner {
|
|
|
540
562
|
s.pending_user_source = null;
|
|
541
563
|
const attachments = opts.attachments ?? [];
|
|
542
564
|
if (attachments.length) {
|
|
543
|
-
const names = attachments.map((a) => str(a.name || a.filename)).filter(Boolean);
|
|
544
565
|
for (let i = s.messages.length - 1; i >= 0; i -= 1) {
|
|
545
566
|
const m = s.messages[i];
|
|
546
567
|
if (m.role === 'user') {
|
|
547
568
|
const content = messageText(m.content);
|
|
548
569
|
const base = msgText.includes('\n\n[Attached files:') ? (msgText.split('\n\n[Attached files:')[0] ?? '').trim() : msgText;
|
|
549
570
|
if (content.includes(base.slice(0, 60)) || msgText.includes(content.slice(0, 60)))
|
|
550
|
-
m.attachments =
|
|
571
|
+
m.attachments = [...attachments];
|
|
551
572
|
break;
|
|
552
573
|
}
|
|
553
574
|
}
|
|
@@ -624,11 +645,10 @@ export class TurnRunner {
|
|
|
624
645
|
const donePayload = {
|
|
625
646
|
session: redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled()),
|
|
626
647
|
usage: doneUsage,
|
|
648
|
+
terminal_state: turnTerminalState(s.messages, streamId),
|
|
627
649
|
};
|
|
628
|
-
if (result.tool_limit_reached)
|
|
629
|
-
donePayload.terminal_state = 'tool_limit_reached';
|
|
650
|
+
if (result.tool_limit_reached)
|
|
630
651
|
donePayload.terminal_reason = 'max_iterations';
|
|
631
|
-
}
|
|
632
652
|
put('done', donePayload);
|
|
633
653
|
for (const [event, payload] of steerEvents)
|
|
634
654
|
put(event, payload);
|
|
@@ -660,7 +680,7 @@ export class TurnRunner {
|
|
|
660
680
|
}
|
|
661
681
|
failed = true;
|
|
662
682
|
if (this.registry.cancelled.has(streamId)) {
|
|
663
|
-
this.finalizeCancelled(s, streamId,
|
|
683
|
+
this.finalizeCancelled(s, streamId, opts.ephemeral);
|
|
664
684
|
put('cancel', this.cancelFrame(sessionId));
|
|
665
685
|
return;
|
|
666
686
|
}
|
|
@@ -777,8 +797,8 @@ export class TurnRunner {
|
|
|
777
797
|
* msg_limit=` (renderable rows, limited payload shape, windowed tool calls, todo state) with the full count.
|
|
778
798
|
*/
|
|
779
799
|
terminalSessionPayload(s) {
|
|
780
|
-
const payload = s.compact(
|
|
781
|
-
const scened = hydrateAnchorActivityScenes(withTurnIds(s.messages), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: true });
|
|
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 });
|
|
782
802
|
const [window, offset] = messageWindowForDisplay(scened, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
|
|
783
803
|
const limited = messagesForLimitedPayload(window);
|
|
784
804
|
payload.messages = limited;
|
|
@@ -804,9 +824,8 @@ export class TurnRunner {
|
|
|
804
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 };
|
|
805
825
|
if (duration !== null)
|
|
806
826
|
errorMessage._turnDuration = Math.round(duration * 1000) / 1000;
|
|
807
|
-
// The
|
|
808
|
-
|
|
809
|
-
errorMessage._terminal_state = str(payload.type);
|
|
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';
|
|
810
829
|
if (payload.type === 'compression_exhausted') {
|
|
811
830
|
// Python `stamp_compression_exhausted_recovery`: durable recovery metadata on the session, the marker, and the frame.
|
|
812
831
|
const recovery = stampCompressionExhaustedRecovery(s, str(payload.message) || label, str(payload.details));
|
|
@@ -873,22 +892,27 @@ export class TurnRunner {
|
|
|
873
892
|
return;
|
|
874
893
|
s.messages.push(partial);
|
|
875
894
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
+
*/
|
|
880
899
|
cancelFrame(sessionId) {
|
|
881
900
|
let snapshot = null;
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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 } : {}) };
|
|
889
909
|
}
|
|
890
|
-
/**
|
|
891
|
-
|
|
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) {
|
|
892
916
|
if (this.registry.writebackOwners.get(s.session_id) !== streamId)
|
|
893
917
|
return false;
|
|
894
918
|
let current = s;
|
|
@@ -908,18 +932,43 @@ export class TurnRunner {
|
|
|
908
932
|
this.deps.store.sessions.delete(current.session_id);
|
|
909
933
|
return true;
|
|
910
934
|
}
|
|
911
|
-
|
|
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
|
+
}
|
|
912
958
|
return true;
|
|
959
|
+
}
|
|
913
960
|
const startedAt = current.pending_started_at;
|
|
914
961
|
this.materializePendingUserTurn(current, buildActiveTurnToken(streamId, current.pending_started_at), streamId);
|
|
962
|
+
const context = settle();
|
|
963
|
+
if (context)
|
|
964
|
+
current.context_messages = dedupeContext(context);
|
|
915
965
|
current.active_stream_id = null;
|
|
916
966
|
current.pending_user_message = null;
|
|
917
967
|
current.pending_attachments = [];
|
|
918
968
|
current.pending_started_at = null;
|
|
919
969
|
current.pending_user_source = null;
|
|
920
970
|
this.appendPartialSnapshot(current, streamId);
|
|
921
|
-
|
|
922
|
-
current.messages.push({ role: 'assistant', content: `**Task cancelled:** ${text}\n\n*${cancelledTurnHint(this.deps.agentName())}*`, _error: true, provider_details: text, provider_details_label: 'Cancellation details', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
|
|
971
|
+
current.messages.push({ role: 'assistant', content: '', _error: true, _terminal_state: 'cancelled', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
|
|
923
972
|
this.persistConsumedSteers(current, streamId, startedAt, this.deps.now());
|
|
924
973
|
try {
|
|
925
974
|
this.deps.store.save(current);
|
|
@@ -946,6 +995,8 @@ export class TurnRunner {
|
|
|
946
995
|
this.writers.delete(streamId);
|
|
947
996
|
}
|
|
948
997
|
this.abortControllers.delete(streamId);
|
|
998
|
+
this.interrupts.delete(streamId);
|
|
999
|
+
this.stopContexts.delete(streamId);
|
|
949
1000
|
this.steers.delete(streamId);
|
|
950
1001
|
this.consumedSteers.delete(streamId);
|
|
951
1002
|
this.lastCompletedTool.delete(streamId);
|
|
@@ -1111,16 +1162,17 @@ export class TurnRunner {
|
|
|
1111
1162
|
}
|
|
1112
1163
|
// Python `_finalize_webui_steers` drained the Agent's pending steer text at cancel time: the interrupt reply
|
|
1113
1164
|
// carries it so queued steers settle as consumed / leftover before the terminal row.
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
leftover = '';
|
|
1122
|
-
}
|
|
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);
|
|
1123
1172
|
}
|
|
1173
|
+
const reply = await interrupt;
|
|
1174
|
+
const leftover = str(reply?.pending_steer);
|
|
1175
|
+
const checkpoint = reply?.checkpoint ?? null;
|
|
1124
1176
|
// Settle the steers first so the persisted cancel carries every consumed one.
|
|
1125
1177
|
const steerEvents = (this.steers.get(streamId) ?? []).length ? this.finalizeSteerEvents(streamId, leftover) : [];
|
|
1126
1178
|
if (sessionId) {
|
|
@@ -1132,7 +1184,7 @@ export class TurnRunner {
|
|
|
1132
1184
|
current = null;
|
|
1133
1185
|
}
|
|
1134
1186
|
if (current?.active_stream_id === streamId) {
|
|
1135
|
-
this.finalizeCancelled(current, streamId,
|
|
1187
|
+
this.finalizeCancelled(current, streamId, run?.ephemeral, checkpoint);
|
|
1136
1188
|
}
|
|
1137
1189
|
}
|
|
1138
1190
|
// The worker may already have written its terminal row while the interrupt was in flight; the stream is then
|
|
@@ -1153,7 +1205,7 @@ export class TurnRunner {
|
|
|
1153
1205
|
};
|
|
1154
1206
|
for (const [event, data] of steerEvents)
|
|
1155
1207
|
emit(event, data);
|
|
1156
|
-
emit('cancel',
|
|
1208
|
+
emit('cancel', this.cancelFrame(sessionId));
|
|
1157
1209
|
this.registry.streams.delete(streamId);
|
|
1158
1210
|
this.registry.liveIds.delete(streamId);
|
|
1159
1211
|
}
|
|
@@ -1411,11 +1463,18 @@ export class TurnRunner {
|
|
|
1411
1463
|
return this.deps.service().setYolo(sessionId, false);
|
|
1412
1464
|
});
|
|
1413
1465
|
}
|
|
1414
|
-
/**
|
|
1415
|
-
|
|
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) {
|
|
1416
1472
|
const entry = this.deps.pending.peekClarify(sessionId, clarifyId);
|
|
1417
1473
|
if (!entry)
|
|
1418
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 };
|
|
1419
1478
|
const sidecar = this.deps.sidecar();
|
|
1420
1479
|
if (!sidecar)
|
|
1421
1480
|
return { ok: false, error: 'The Agent sidecar is not running; retry in a moment.' };
|
|
@@ -1431,12 +1490,16 @@ export class TurnRunner {
|
|
|
1431
1490
|
const { head } = this.deps.pending.resolveClarify(sessionId, str(entry.clarify_id));
|
|
1432
1491
|
if (head)
|
|
1433
1492
|
this.emitToSession(sessionId, 'clarify', head);
|
|
1434
|
-
return { ok: true };
|
|
1493
|
+
return { ok: true, response };
|
|
1435
1494
|
}
|
|
1436
1495
|
}
|
|
1437
1496
|
function previousStartedAt(s, run) {
|
|
1438
1497
|
return typeof s.pending_started_at === 'number' && s.pending_started_at > 0 ? s.pending_started_at : (run?.started_at ?? Date.now() / 1000);
|
|
1439
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
|
+
}
|
|
1440
1503
|
function dedupeContext(messages) {
|
|
1441
1504
|
const out = [];
|
|
1442
1505
|
let lastKey = null;
|