@maudecode/talaria-web 1.17.2 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release.json +8 -8
- package/dist/api/auth-raw.d.ts +1 -1
- package/dist/api/auth-raw.js +4 -4
- package/dist/api/auth-raw.js.map +1 -1
- package/dist/api/automation-raw.d.ts +6 -0
- package/dist/api/automation-raw.js +53 -6
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +26 -1
- package/dist/api/automation-router.js +16 -2
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +649 -15
- package/dist/api/chat-router.js +52 -50
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +35 -1
- package/dist/api/git-router.js +18 -19
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +8 -0
- package/dist/api/raw-routes.js +247 -36
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +10 -0
- package/dist/api/router.js +10 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +1492 -115
- package/dist/api/sessions-router.js +134 -8
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +31 -0
- package/dist/api/settings-router.js +8 -3
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +5 -0
- package/dist/api/sse-routes.js +50 -3
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -23
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +356 -15
- package/dist/api/tools-router.js +148 -19
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +35 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +5 -2
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +5 -2
- package/dist/auth/oidc.js +8 -7
- package/dist/auth/oidc.js.map +1 -1
- package/dist/bin/talaria-web.js +1 -1
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/dotenv.js +1 -0
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.js +3 -2
- package/dist/config/agent-config.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/http/context.d.ts +17 -3
- package/dist/http/context.js +31 -4
- package/dist/http/context.js.map +1 -1
- package/dist/http/csp.d.ts +2 -1
- package/dist/http/csp.js +3 -2
- package/dist/http/csp.js.map +1 -1
- package/dist/profiles/profiles.d.ts +10 -0
- package/dist/profiles/profiles.js +40 -2
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +6 -2
- package/dist/providers/catalog.js +30 -11
- package/dist/providers/catalog.js.map +1 -1
- package/dist/raster-image.d.ts +7 -0
- package/dist/raster-image.js +267 -0
- package/dist/raster-image.js.map +1 -0
- package/dist/redact.js +327 -40
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +89 -23
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +2 -9
- package/dist/sessions/background-tasks.js +2 -12
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +15 -4
- package/dist/sessions/cli-sessions.js +120 -29
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +48 -8
- package/dist/sessions/completions.js +222 -26
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/export.d.ts +5 -0
- package/dist/sessions/export.js +30 -0
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-watcher.d.ts +7 -3
- package/dist/sessions/gateway-watcher.js +31 -19
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/handoff.d.ts +32 -0
- package/dist/sessions/handoff.js +114 -0
- package/dist/sessions/handoff.js.map +1 -0
- package/dist/sessions/list.d.ts +7 -1
- package/dist/sessions/list.js +13 -5
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +2 -4
- package/dist/sessions/merge.js +8 -15
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/recovery.d.ts +22 -0
- package/dist/sessions/recovery.js +506 -0
- package/dist/sessions/recovery.js.map +1 -0
- package/dist/sessions/relay.d.ts +21 -2
- package/dist/sessions/relay.js +156 -11
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +108 -6
- package/dist/sessions/service.js +403 -29
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +6 -0
- package/dist/sessions/session.js +40 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/state-db-worker.d.ts +1 -0
- package/dist/sessions/state-db-worker.js +33 -0
- package/dist/sessions/state-db-worker.js.map +1 -0
- package/dist/sessions/state-db.d.ts +61 -0
- package/dist/sessions/state-db.js +291 -0
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.d.ts +7 -0
- package/dist/sessions/store.js +31 -22
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/titles.d.ts +2 -0
- package/dist/sessions/titles.js +4 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +3 -3
- package/dist/sessions/tool-display.js +11 -37
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +1 -1
- package/dist/sessions/turn-context.js +11 -7
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +37 -2
- package/dist/sessions/turn.js +182 -49
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/wakeup-pause.d.ts +59 -0
- package/dist/sessions/wakeup-pause.js +181 -0
- package/dist/sessions/wakeup-pause.js.map +1 -0
- package/dist/settings.js +5 -7
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/startup.d.ts +13 -5
- package/dist/startup.js +48 -0
- package/dist/startup.js.map +1 -1
- package/dist/test/ts-worker-hooks.d.ts +1 -0
- package/dist/test/ts-worker-hooks.js +24 -0
- package/dist/test/ts-worker-hooks.js.map +1 -0
- package/dist/text/diff.d.ts +9 -14
- package/dist/text/diff.js +74 -108
- package/dist/text/diff.js.map +1 -1
- package/dist/tools/crons.js +45 -8
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +53 -0
- package/dist/tools/dashboard-plugins.js +188 -0
- package/dist/tools/dashboard-plugins.js.map +1 -0
- package/dist/tools/extensions.d.ts +13 -1
- package/dist/tools/extensions.js +132 -60
- package/dist/tools/extensions.js.map +1 -1
- package/dist/tools/health.d.ts +32 -0
- package/dist/tools/health.js +111 -19
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/mcp.d.ts +13 -0
- package/dist/tools/mcp.js +88 -0
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/project-os.d.ts +11 -0
- package/dist/tools/project-os.js +267 -0
- package/dist/tools/project-os.js.map +1 -0
- package/dist/tools/skills.d.ts +5 -1
- package/dist/tools/skills.js +5 -3
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/updates.d.ts +27 -9
- package/dist/tools/updates.js +163 -51
- package/dist/tools/updates.js.map +1 -1
- package/dist/tools/wiki.d.ts +68 -0
- package/dist/tools/wiki.js +413 -0
- package/dist/tools/wiki.js.map +1 -0
- package/dist/workspace/escape.d.ts +61 -0
- package/dist/workspace/escape.js +149 -0
- package/dist/workspace/escape.js.map +1 -0
- package/dist/workspace/extract.d.ts +22 -0
- package/dist/workspace/extract.js +308 -0
- package/dist/workspace/extract.js.map +1 -0
- package/dist/workspace/fs.d.ts +12 -0
- package/dist/workspace/fs.js +92 -23
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/git.d.ts +18 -0
- package/dist/workspace/git.js +38 -25
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media.d.ts +4 -4
- package/dist/workspace/media.js +16 -45
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/rollback.d.ts +34 -20
- package/dist/workspace/rollback.js +171 -265
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/unzip.js +4 -1
- package/dist/workspace/unzip.js.map +1 -1
- package/dist/workspace/upload.d.ts +7 -4
- package/dist/workspace/upload.js +5 -2
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -1
- package/dist/workspace/worktrees.js +14 -15
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/scripts/replay_sidecar.py +24 -0
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +13 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +39 -5
- package/sidecar/talaria_sidecar/methods/chat.py +9 -5
- package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
- package/sidecar/talaria_sidecar/methods/commands.py +81 -0
- package/sidecar/talaria_sidecar/methods/cron.py +29 -2
- package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
- package/sidecar/talaria_sidecar/methods/kanban.py +28 -7
- package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
- package/sidecar/talaria_sidecar/methods/process.py +58 -4
- package/sidecar/talaria_sidecar/methods/profiles.py +77 -7
- package/sidecar/talaria_sidecar/methods/providers.py +27 -8
- package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
- package/sidecar/talaria_sidecar/runtime.py +17 -1
- package/static/dist/FILES.txt +127 -126
- package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
- package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
- package/static/dist/assets/Dialog-g355qxhp.js +1 -0
- package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
- package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
- package/static/dist/assets/{Markdown-mdwlvj3w.js → Markdown-j4kut4j2.js} +114 -114
- package/static/dist/assets/{RightPanel-ftvrjlcp.js → RightPanel-ohlsdzfl.js} +1 -1
- package/static/dist/assets/Select-kddq75qc.js +1 -0
- package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
- package/static/dist/assets/{SettingsLayout-jonjefmj.js → SettingsLayout-o9bv4mgb.js} +1 -1
- package/static/dist/assets/{States-nmejrwrm.js → States-eq6fe29m.js} +1 -1
- package/static/dist/assets/Toaster-pc247u9f.js +1 -0
- package/static/dist/assets/{_app-gdidzd94.js → _app-f8n1i0m1.js} +1 -1
- package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-gfbzpoou.js} +1 -1
- package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
- package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
- package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
- package/static/dist/assets/_app.logs-i69qds98.js +1 -0
- package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
- package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-fn1bhde6.js} +1 -1
- package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
- package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
- package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
- package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
- package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
- package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
- package/static/dist/assets/{back-bemaer09.js → back-h7aun7ew.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
- package/static/dist/assets/{cancel-drd0g6d9.js → cancel-fhi0vcnu.js} +1 -1
- package/static/dist/assets/channel-b2zo73z1.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
- package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-dnau702j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
- package/static/dist/assets/{copied-ima3pysb.js → copied-hcplv46p.js} +1 -1
- package/static/dist/assets/{copy-fw5b1pc6.js → copy-magtofyp.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
- package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
- package/static/dist/assets/endpoints-klbxod4t.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
- package/static/dist/assets/host-i828xuo7.js +1 -0
- package/static/dist/assets/index-eh8zilnv.css +2 -0
- package/static/dist/assets/{index-cxq58xb1.js → index-r648f4o1.js} +5 -5
- package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-af0lvgjx.js → lazyRouteComponent-e1y4t2i9.js} +1 -1
- package/static/dist/assets/{line-osxq6fq7.js → line-meizqsvn.js} +1 -1
- package/static/dist/assets/{link-jyxicdhg.js → link-oot2yn73.js} +1 -1
- package/static/dist/assets/login-gu28l9ou.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-ntne45i9.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
- package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
- package/static/dist/assets/{onboarding_model_label-dp7xffoh.js → onboarding_model_label-ftpjdwqk.js} +1 -1
- package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
- package/static/dist/assets/{refresh-b4uams1i.js → refresh-abtu93ke.js} +1 -1
- package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-hlwb02qv.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
- package/static/dist/assets/{saved-kg52csy7.js → saved-girnabkf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-ej0iapxe.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
- package/static/dist/assets/share._token-kl0r7ido.js +2 -0
- package/static/dist/assets/{sliders-horizontal-fj18it47.js → sliders-horizontal-n1zm34qq.js} +1 -1
- package/static/dist/assets/sse-iwku0sjo.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
- package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
- package/static/dist/assets/{tts-lxdxnmjd.js → units-kgxzxfhi.js} +1 -1
- package/static/dist/assets/{untitled-ovpfthg4.js → untitled-cikfjntn.js} +1 -1
- package/static/dist/assets/{useForm-bcblv1rx.js → useForm-kqoqg0k9.js} +1 -1
- package/static/dist/assets/useLocale-o59tbiea.js +1 -0
- package/static/dist/assets/{useParams-lhg566ew.js → useParams-b4dq6n4q.js} +1 -1
- package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-ipfmv3he.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
- package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
- package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
- package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
- package/static/dist/assets/Select-bilaypej.js +0 -1
- package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
- package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
- package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
- package/static/dist/assets/_app.insights-luywlarz.js +0 -1
- package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
- package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
- package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
- package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
- package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
- package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
- package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
- package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
- package/static/dist/assets/channel-cpdgf867.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
- package/static/dist/assets/endpoints-d33siokx.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
- package/static/dist/assets/index-hgtx08n5.css +0 -2
- package/static/dist/assets/login-ec21t39x.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
- package/static/dist/assets/onboarding-btnltvyz.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
- package/static/dist/assets/share._token-f97e94r0.js +0 -2
- package/static/dist/assets/sse-fwpqtzbg.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
- package/static/dist/assets/todoStore-eginf9h4.js +0 -1
- package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
package/dist/sessions/service.js
CHANGED
|
@@ -3,17 +3,20 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { rmSync } from 'node:fs';
|
|
4
4
|
import { buildActiveTurnToken, copyJson, redactSessionData, redactValue, stripPublicInternalFields, withSceneToolDisplay } from '../redact.js';
|
|
5
5
|
import { DraftVersionConflict, normalizeDraftVersion } from './drafts.js';
|
|
6
|
-
import { allSessions, buildSessionListPayload, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchMessageText, sessionSearchPreview, sessionSearchTerms } from './list.js';
|
|
6
|
+
import { allSessions, buildSessionListPayload, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, ownerLockCandidates, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchMessageText, sessionSearchPreview, sessionSearchTerms } from './list.js';
|
|
7
7
|
import { anchorSceneIntOrNull, fullToolResult, hydrateAnchorActivityScenes, normalizeAnchorSceneMessageRef, readAnchorSceneRows, storeAnchorScene, withTurnIds } from './anchor.js';
|
|
8
|
-
import { isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
|
|
8
|
+
import { COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
|
|
9
9
|
import { SessionBusy, SessionNotFound, statSignature } from './store.js';
|
|
10
10
|
import { UNSETTLED_TODO_KEY, attachTodoState } from './todo.js';
|
|
11
11
|
import { isClaudeCodeSessionId } from './claude-code.js';
|
|
12
|
-
import {
|
|
12
|
+
import { auditSessionRecovery, repairSafeSessionRecovery } from './recovery.js';
|
|
13
|
+
import { agentSessionRowsExisting, agentSessionRowsExistingAsync, CONVERSATION_ROUND_THRESHOLD, countConversationRounds, stateDbCompressionLineage, stateDbTailToolContent, stateDbLineageReport, stateDbSessionMessages, stateDbSessionRead, stateDbSessionRow, stateDbSessionSources, stateDbSessionSourcesAsync, stateDbTimestampSeconds } from './state-db.js';
|
|
14
|
+
import { completionIncomplete, fallbackHandoffSummary, HANDOFF_SYSTEM_PROMPT, handoffMarker, handoffPayload, handoffTranscript, messageHandoffPayload, sameHandoff } from './handoff.js';
|
|
13
15
|
import { anchorMessageKey, anchorSummary, CompressionJobs, compressionReference, visibleMessagesForAnchor } from './compress.js';
|
|
14
16
|
import { SidecarError } from '../sidecar/client.js';
|
|
15
|
-
import { agentSteerText, attachedFilesPrompt, attachmentObjects, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext,
|
|
17
|
+
import { agentSteerText, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
|
|
16
18
|
import { withBackgroundUpdates } from './background-updates.js';
|
|
19
|
+
import { sourceKind } from './source-kind.js';
|
|
17
20
|
import { withBackgroundLinks } from './background-tasks.js';
|
|
18
21
|
import { messagesForLimitedPayload, messageWindowForDisplay, MAX_MSG_LIMIT, parseMsgLimit, toolCallsForMessageWindow } from './window.js';
|
|
19
22
|
import { redactText } from '../redact.js';
|
|
@@ -64,6 +67,40 @@ export class SessionService {
|
|
|
64
67
|
this.compressionJobs = new CompressionJobs();
|
|
65
68
|
}
|
|
66
69
|
get store() { return this.deps.store; }
|
|
70
|
+
/**
|
|
71
|
+
* TAL-577: user-turn admission shared by `chat.start` and server-side wakeups, after `ensureAgentRuntimeCurrent`.
|
|
72
|
+
* A requested workspace must be trusted; the session's own one, when its directory is gone, falls back to the last one.
|
|
73
|
+
*/
|
|
74
|
+
turnWorkspace(s, requested) {
|
|
75
|
+
const ws = this.deps.workspaces;
|
|
76
|
+
if (requested !== undefined && requested !== null && requested !== '') {
|
|
77
|
+
try {
|
|
78
|
+
return ws.resolveTrusted(str(requested), s.profile);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new HttpFailure(400, error.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const [workspace, recovered] = ws.resolveImplicitWithRecovery(s.workspace, (p) => ws.lastWorkspace(p), s.profile);
|
|
86
|
+
if (recovered)
|
|
87
|
+
s.workspace = workspace;
|
|
88
|
+
return workspace;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw new HttpFailure(400, error.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** A `@provider:` id is split; any other model an explicit pick did not just choose is checked against the catalog (TAL-542). */
|
|
95
|
+
turnModel(s, body = {}) {
|
|
96
|
+
const requestedModel = str(body.model) || s.model;
|
|
97
|
+
const requestedProvider = 'model_provider' in body ? body.model_provider : s.model_provider;
|
|
98
|
+
const [model, provider] = this.deps.modelStateFromRequest(requestedModel, requestedProvider, s.model_provider);
|
|
99
|
+
if (model !== requestedModel || !model || body.explicit_model_pick === true)
|
|
100
|
+
return [model, provider, model !== requestedModel];
|
|
101
|
+
const repaired = this.deps.repairSessionModel?.(s.profile ?? null, model, provider);
|
|
102
|
+
return repaired ? [repaired[0], repaired[1], true] : [model, provider, false];
|
|
103
|
+
}
|
|
67
104
|
/**
|
|
68
105
|
* TAL-186: rewrites a session's media references to their URLs. A local path is rewritten only when `/api/media`
|
|
69
106
|
* serves it for this session, under the same resolution, so every rewritten reference loads.
|
|
@@ -150,6 +187,52 @@ export class SessionService {
|
|
|
150
187
|
}
|
|
151
188
|
/** TAL-358: batched `sessions.source` owners from the active profile's state.db. */
|
|
152
189
|
stateDbSources = (ids) => stateDbSessionSources(join(this.deps.profileHome(this.deps.activeProfile()), 'state.db'), ids);
|
|
190
|
+
/**
|
|
191
|
+
* TAL-590 (Python #3238): the read-only CLI or API-server imports among `rows` whose active-profile state.db row is
|
|
192
|
+
* gone, so their source was deleted outside Web and no Web action could remove them. An unreadable state.db proves
|
|
193
|
+
* nothing (`agentSessionRowsExisting`); another profile's session or one with a run the runtime still holds is never
|
|
194
|
+
* an orphan, while a stream id a crash left behind does not count as one.
|
|
195
|
+
*/
|
|
196
|
+
orphanedImports(rows) {
|
|
197
|
+
const candidates = this.orphanCandidates(rows);
|
|
198
|
+
if (!candidates.length)
|
|
199
|
+
return new Set();
|
|
200
|
+
const existing = agentSessionRowsExisting(this.stateDbPath(null), candidates);
|
|
201
|
+
return new Set(candidates.filter((sid) => !existing.has(sid)));
|
|
202
|
+
}
|
|
203
|
+
orphanCandidates(rows) {
|
|
204
|
+
return rows.filter((r) => r.read_only && ['cli', 'api'].includes(sourceKind(r)) && this.visibleToActiveProfile(str(r.profile) || null) && !this.activeRunBlocking(str(r.session_id)))
|
|
205
|
+
.map((r) => str(r.session_id));
|
|
206
|
+
}
|
|
207
|
+
isOrphanedImport(sid) {
|
|
208
|
+
try {
|
|
209
|
+
return this.orphanedImports([this.store.get(sid, { metadataOnly: true }).compact()]).size > 0;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The list's owner locks and orphan prune (an orphan's sidecar goes, as nothing else ever removes it), with their
|
|
217
|
+
* state.db reads made on the listing worker up front so the synchronous build never opens state.db (TAL-920).
|
|
218
|
+
*/
|
|
219
|
+
async listStateDbLookups(rows) {
|
|
220
|
+
const dbPath = this.stateDbPath(null);
|
|
221
|
+
const ownerIds = ownerLockCandidates(rows);
|
|
222
|
+
const owners = ownerIds.length ? await stateDbSessionSourcesAsync(dbPath, ownerIds) : new Map();
|
|
223
|
+
const checked = new Set(this.orphanCandidates(rows));
|
|
224
|
+
const existing = checked.size ? await agentSessionRowsExistingAsync(dbPath, checked) : new Set();
|
|
225
|
+
return {
|
|
226
|
+
stateDbSources: () => owners,
|
|
227
|
+
pruneOrphanedImports: (current) => {
|
|
228
|
+
// Only an id the worker checked can be an orphan; a row that became a candidate meanwhile waits for the next list.
|
|
229
|
+
const orphans = new Set(this.orphanCandidates(current).filter((sid) => checked.has(sid) && !existing.has(sid)));
|
|
230
|
+
for (const sid of orphans)
|
|
231
|
+
this.store.deleteFiles(sid);
|
|
232
|
+
return orphans;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
153
236
|
/** The persisted flag folded with the state.db owner lock (TAL-358): the one read-only rule for mutation gates and the wire. */
|
|
154
237
|
isReadOnly(s) {
|
|
155
238
|
if (s.read_only)
|
|
@@ -178,6 +261,7 @@ export class SessionService {
|
|
|
178
261
|
const synth = this.claimOrSynthesizeCliSession(sid);
|
|
179
262
|
if (synth.session && synth.reason === 'materialized') {
|
|
180
263
|
this.store.save(synth.session);
|
|
264
|
+
this.deps.titleClaimed?.(synth.session);
|
|
181
265
|
return synth.session;
|
|
182
266
|
}
|
|
183
267
|
if (synth.session)
|
|
@@ -242,7 +326,7 @@ export class SessionService {
|
|
|
242
326
|
* turn started from but the transcript lacks (a CLI continuation read into its context) are kept before the turn.
|
|
243
327
|
*/
|
|
244
328
|
settleStateDb(s, turn) {
|
|
245
|
-
const read = this.stateDbRead(s);
|
|
329
|
+
const read = turn.read ?? this.stateDbRead(s);
|
|
246
330
|
const startedWith = new Set(turn.previousContext.flatMap((m) => (typeof m._state_db_row_id === 'number' ? [m._state_db_row_id] : [])));
|
|
247
331
|
const earlier = mergeSessionMessagesAppendOnly(s.messages, read.rows.filter((m) => typeof m._state_db_row_id === 'number' && startedWith.has(m._state_db_row_id)), { stateDbSeenId: -1 }).slice(s.messages.length);
|
|
248
332
|
const added = new Map();
|
|
@@ -296,6 +380,30 @@ export class SessionService {
|
|
|
296
380
|
this.markStateDbSeen(s);
|
|
297
381
|
}
|
|
298
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* TAL-921: the model context a turn leaves when it ends without its settled result (an error, a Stop, a dead run), as a
|
|
385
|
+
* Stop's: the Agent's rows when they hold the turn's prompt, else its own rows among `stateRows` (the state.db rows
|
|
386
|
+
* committed after it started), then the prose that streamed past them. Null when it captured nothing the model can use.
|
|
387
|
+
*/
|
|
388
|
+
unsettledTurnContext(turn) {
|
|
389
|
+
const reported = turn.agentRows && checkpointTurnStart(turn.agentRows, turn.msgText, turn.historyLength) !== null ? turn.agentRows : null;
|
|
390
|
+
const own = reported ? null : runStateDbRows(turn.stateRows, turn.msgText);
|
|
391
|
+
const checkpoint = reported ?? (own && [...turn.previousContext, ...own]);
|
|
392
|
+
const context = stoppedTurnContext(turn.previousContext, checkpoint, turn.prompt, turn.msgText, turn.streamedText, turn.historyLength);
|
|
393
|
+
if (!context)
|
|
394
|
+
return null;
|
|
395
|
+
if (!own || !checkpoint || checkpointTurnStart(checkpoint, turn.msgText, turn.historyLength) === null)
|
|
396
|
+
return dedupeContext(context);
|
|
397
|
+
// The context holds the turn's own state.db rows from its prompt on. The Agent persisted the prompt's text only, so the
|
|
398
|
+
// prompt row carries what the turn sent (attached files, images). This turn's settlement covers the rows: without
|
|
399
|
+
// their ids, the next one never reads them as a CLI continuation the transcript lacks.
|
|
400
|
+
const at = turn.previousContext.length;
|
|
401
|
+
return dedupeContext(context.map((m, i) => {
|
|
402
|
+
if (i === at)
|
|
403
|
+
return { role: 'user', content: turn.prompt, ...(m.timestamp != null ? { timestamp: m.timestamp } : {}) };
|
|
404
|
+
return i > at && i < at + own.length ? Object.fromEntries(Object.entries(m).filter(([k]) => k !== '_state_db_row_id' && k !== '_state_db_generation')) : m;
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
299
407
|
/**
|
|
300
408
|
* Python `reconciled_state_db_messages_for_session(prefer_context=True)`: the model history is the owner context
|
|
301
409
|
* extended append-only with the Agent's state.db rows (a CLI continuation of this session reaches the model), except
|
|
@@ -314,12 +422,22 @@ export class SessionService {
|
|
|
314
422
|
const pending = this.pendingTurn(s);
|
|
315
423
|
return pending ? withPendingUserTurn(context, pending) : context;
|
|
316
424
|
}
|
|
425
|
+
/** Python `_is_messaging_session_id`: from the WebUI metadata or the Agent's row. */
|
|
426
|
+
isMessagingSession(sid) {
|
|
427
|
+
try {
|
|
428
|
+
if (isMessagingSessionRecord(this.store.get(sid, { metadataOnly: true }).compact()))
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
catch { /* absent */ }
|
|
432
|
+
const meta = this.lookupCliMeta(sid);
|
|
433
|
+
return meta !== null && isMessagingSessionRecord(meta);
|
|
434
|
+
}
|
|
317
435
|
/** Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile. */
|
|
318
436
|
lookupCliMeta(sid) {
|
|
319
437
|
try {
|
|
320
438
|
if (isClaudeCodeSessionId(sid))
|
|
321
439
|
return this.claudeCodeRows().find((r) => str(r.session_id) === sid) ?? null;
|
|
322
|
-
return this.deps.
|
|
440
|
+
return this.deps.cliSessionsSync(this.deps.activeProfile(), { sourceFilter: null }).rows.find((r) => str(r.session_id) === sid) ?? null;
|
|
323
441
|
}
|
|
324
442
|
catch {
|
|
325
443
|
return null;
|
|
@@ -498,7 +616,8 @@ export class SessionService {
|
|
|
498
616
|
}
|
|
499
617
|
this.clearStaleStreamState(s);
|
|
500
618
|
const journaled = loadMessages ? this.journaledActiveTurn(s) : null;
|
|
501
|
-
|
|
619
|
+
const stateRows = loadMessages ? this.stateDbRows(s) : [];
|
|
620
|
+
let transcript = loadMessages ? this.mergedTranscript(s, s.messages, stateRows) : [];
|
|
502
621
|
const pending = loadMessages ? this.pendingTurn(s) : null;
|
|
503
622
|
if (pending)
|
|
504
623
|
transcript = withPendingUserTurn(transcript, pending);
|
|
@@ -572,10 +691,42 @@ export class SessionService {
|
|
|
572
691
|
raw.read_only = this.isReadOnly(s);
|
|
573
692
|
raw.assistant_name = this.assistantName(s);
|
|
574
693
|
raw.workspace_name = this.workspaceNames()(s);
|
|
694
|
+
raw.branched_from = this.branchedFrom(s);
|
|
575
695
|
withSessionWireFlags(raw, activeStreamIds);
|
|
696
|
+
// A metadata-only read lacks the history the gate counts, so it carries no flag.
|
|
697
|
+
if (loadMessages)
|
|
698
|
+
raw.can_compress = this.canCompress(s, raw.read_only === true, stateRows);
|
|
576
699
|
raw.pending_steers = raw.active_stream_id ? (this.deps.runtime.pendingSteers?.(str(raw.active_stream_id)) ?? []) : [];
|
|
577
700
|
return redactSessionData(raw, this.deps.redactEnabled());
|
|
578
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* TAL-454: the chat `/branch` copied, while it still loads (archived included). A compression continuation is also a
|
|
704
|
+
* fork of its parent but not a branch, so it links nowhere.
|
|
705
|
+
*/
|
|
706
|
+
branchedFrom(s) {
|
|
707
|
+
const parentId = s.parent_session_id;
|
|
708
|
+
if (!parentId || str(s.session_source).trim().toLowerCase() !== 'fork' || s.compression_recovery_source_session_id)
|
|
709
|
+
return null;
|
|
710
|
+
let title;
|
|
711
|
+
try {
|
|
712
|
+
const parent = this.store.get(parentId, { metadataOnly: true, promote: false, cacheOnMiss: false });
|
|
713
|
+
if (!this.visibleToActiveProfile(parent.profile))
|
|
714
|
+
return null;
|
|
715
|
+
title = parent.title;
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// A read-only foreign parent (a cron run) has no sidecar and opens from the active profile's state.db while it
|
|
719
|
+
// has transcript rows and is not a deleted chat (`claimOrSynthesizeCliSession`'s `was_webui` and `no_foreign_state`).
|
|
720
|
+
const dbPath = this.stateDbPath(null);
|
|
721
|
+
const row = stateDbSessionRow(dbPath, parentId);
|
|
722
|
+
if (!row || this.store.wasDeleted(parentId) || this.indexMarksWasWebui(parentId))
|
|
723
|
+
return null;
|
|
724
|
+
if (!stateDbSessionMessages(dbPath, parentId, { stitch: true }).length)
|
|
725
|
+
return null;
|
|
726
|
+
title = row.title;
|
|
727
|
+
}
|
|
728
|
+
return { session_id: parentId, title: str(redactText(str(title) || 'Untitled', this.deps.redactEnabled())) };
|
|
729
|
+
}
|
|
579
730
|
/**
|
|
580
731
|
* TAL-316: the active run whose journal can replay its output from the start. The detail then leaves that output to the
|
|
581
732
|
* replay; with no active run, no identifiable prompt, or no complete journal, it returns the persisted transcript unchanged.
|
|
@@ -674,7 +825,8 @@ export class SessionService {
|
|
|
674
825
|
const read = this.stateDbRead(target);
|
|
675
826
|
const runStart = target.pending_started_at || events[0]?.created_at || this.deps.now();
|
|
676
827
|
const before = read.rows.filter((m) => Number(m.timestamp) < runStart);
|
|
677
|
-
const
|
|
828
|
+
const runRows = read.rows.filter((m) => Number(m.timestamp) >= runStart);
|
|
829
|
+
const ownRows = target.hasPendingPrompt ? runStateDbRows(runRows, pendingText) : null;
|
|
678
830
|
// Without this turn's rows: an eager save already put its prompt in the transcript a fresh context falls back to.
|
|
679
831
|
const previousContext = this.modelContext(target, before).filter((m) => m._turn_id !== turnId);
|
|
680
832
|
if (target.hasPendingPrompt) {
|
|
@@ -684,16 +836,16 @@ export class SessionService {
|
|
|
684
836
|
target.messages.push({ role: 'user', content: pendingText, timestamp: Math.trunc(startedAt), ...(attachments.length ? { attachments } : {}), _recovered: true, _source: target.pending_user_source ?? 'webui', _turn_id: turnId });
|
|
685
837
|
// The model context settles as a Stop's does: the Agent's own rows when it committed the prompt, else the prompt it
|
|
686
838
|
// was sent; then the prose that streamed past them.
|
|
687
|
-
const prompt = attachedFilesPrompt(
|
|
839
|
+
const prompt = attachedFilesPrompt(pendingText, attachments);
|
|
688
840
|
const streamed = output.map((m) => str(m.content)).filter(Boolean).join('\n\n');
|
|
689
|
-
target.context_messages =
|
|
841
|
+
target.context_messages = this.unsettledTurnContext({ previousContext, prompt, msgText: pendingText, streamedText: streamed, historyLength: previousContext.length, stateRows: runRows }) ?? dedupeContext([...structuredClone(previousContext), { role: 'user', content: prompt }]);
|
|
690
842
|
}
|
|
691
843
|
if (target.hasPendingPrompt || output.length) {
|
|
692
844
|
target.messages.push(...output);
|
|
693
845
|
if (!answered)
|
|
694
846
|
target.messages.push({ role: 'assistant', content: '**Interrupted:** The reply was interrupted before it could be saved.', timestamp: Math.trunc(this.deps.now()), _error: true, _turn_id: turnId });
|
|
695
847
|
// Settled like a turn: the Agent's own rows are covered by the recovered turn; without them, every row past the run's start is.
|
|
696
|
-
this.settleStateDb(target, { turnId, previousContext, agentRows: ownRows, startId: stateDbSeenId(before) ?? (read.idCapable ? 0 : null) });
|
|
848
|
+
this.settleStateDb(target, { turnId, previousContext, agentRows: ownRows, startId: stateDbSeenId(before) ?? (read.idCapable ? 0 : null), read });
|
|
697
849
|
}
|
|
698
850
|
target.active_stream_id = null;
|
|
699
851
|
target.pending_user_message = null;
|
|
@@ -717,16 +869,18 @@ export class SessionService {
|
|
|
717
869
|
return true;
|
|
718
870
|
}
|
|
719
871
|
// ── list / search ────────────────────────────────────────────────────────
|
|
720
|
-
list(params) {
|
|
872
|
+
async list(params) {
|
|
721
873
|
const activeProfile = this.deps.activeProfile();
|
|
722
874
|
const wantState = params.showCliSessions || params.showCronSessions || params.showWebhookSessions || params.showKanbanSessions;
|
|
723
875
|
// Python reads every profile's state.db under all_profiles; this port projects the active profile only.
|
|
724
|
-
const cliRead = wantState ? this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
|
|
876
|
+
const cliRead = wantState ? await this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
|
|
725
877
|
const claudeCodeRows = params.showCliSessions && params.showClaudeCodeSessions ? this.claudeCodeRows() : [];
|
|
726
878
|
const cliRows = cliRead ? [...cliRead.rows, ...claudeCodeRows] : undefined;
|
|
727
879
|
const gatewayIdentity = loadGatewaySessionIdentityMap(join(this.deps.profileHome(activeProfile), 'sessions', 'sessions.json'));
|
|
728
880
|
const truncatedSources = cliRead?.truncated;
|
|
729
|
-
const
|
|
881
|
+
const webuiRows = allSessions(this.store, { sidebarMetadataOnly: true });
|
|
882
|
+
const lookups = await this.listStateDbLookups(webuiRows);
|
|
883
|
+
const payload = buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), gatewayIdentity, ...lookups, webuiRows, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch });
|
|
730
884
|
return sessionListResponse(payload, this.deps.runtime, this.deps.redactEnabled(), this.deps.now(), this.workspaceNames());
|
|
731
885
|
}
|
|
732
886
|
search(q, opts) {
|
|
@@ -770,8 +924,8 @@ export class SessionService {
|
|
|
770
924
|
* archived, projection and order), narrowed to `projectId` (`none`: rows without a project), then matched on the
|
|
771
925
|
* title, then the metadata the row shows (workspace path and name, model, provider, profile, source label), then message content.
|
|
772
926
|
*/
|
|
773
|
-
sidebarSearch(q, params, opts) {
|
|
774
|
-
let rows = this.list(params).body.sessions;
|
|
927
|
+
async sidebarSearch(q, params, opts) {
|
|
928
|
+
let rows = (await this.list(params)).body.sessions;
|
|
775
929
|
if (opts.projectId === 'none')
|
|
776
930
|
rows = rows.filter((r) => !r.project_id);
|
|
777
931
|
else if (opts.projectId)
|
|
@@ -916,7 +1070,8 @@ export class SessionService {
|
|
|
916
1070
|
markSessionTitleGenerated(current);
|
|
917
1071
|
this.store.save(current, { touchUpdatedAt: false });
|
|
918
1072
|
});
|
|
919
|
-
|
|
1073
|
+
// An explicit regenerate is user-requested, so state.db takes it over a manual name.
|
|
1074
|
+
await this.deps.syncTitle(current, true);
|
|
920
1075
|
this.publish(eventReason, current.profile, current.session_id);
|
|
921
1076
|
return current;
|
|
922
1077
|
}
|
|
@@ -926,7 +1081,7 @@ export class SessionService {
|
|
|
926
1081
|
applySessionTitleRename(s, rawTitle);
|
|
927
1082
|
this.store.save(s);
|
|
928
1083
|
});
|
|
929
|
-
await this.deps.syncTitle(s);
|
|
1084
|
+
await this.deps.syncTitle(s, true);
|
|
930
1085
|
this.publish('session_rename', s.profile, s.session_id);
|
|
931
1086
|
return { session: this.wireRow(s) };
|
|
932
1087
|
}
|
|
@@ -1113,12 +1268,146 @@ export class SessionService {
|
|
|
1113
1268
|
compressionTarget(sid) {
|
|
1114
1269
|
this.rejectSubagent(sid, 'compressed');
|
|
1115
1270
|
const s = this.mutationTarget(sid, 'compressed');
|
|
1271
|
+
return { s, history: this.compressionHistory(s) };
|
|
1272
|
+
}
|
|
1273
|
+
/** The loaded session's own compression guards: no running turn and enough model history. */
|
|
1274
|
+
compressionHistory(s, stateRows) {
|
|
1116
1275
|
if (s.active_stream_id)
|
|
1117
1276
|
throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
|
|
1118
|
-
const history = sanitizeMessagesForApi(this.modelContext(s));
|
|
1277
|
+
const history = sanitizeMessagesForApi(this.modelContext(s, stateRows));
|
|
1119
1278
|
if (history.length < 4)
|
|
1120
1279
|
throw new HttpFailure(400, 'Not enough conversation to compress (need at least 4 messages).');
|
|
1121
|
-
return
|
|
1280
|
+
return history;
|
|
1281
|
+
}
|
|
1282
|
+
/** TAL-614: whether a manual compression would start now: `compressionTarget`'s gates, with `readOnly` the wire flag. */
|
|
1283
|
+
canCompress(s, readOnly, stateRows) {
|
|
1284
|
+
if (readOnly || isClaudeCodeSessionId(s.session_id))
|
|
1285
|
+
return false;
|
|
1286
|
+
try {
|
|
1287
|
+
this.compressionHistory(s, stateRows);
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
catch (error) {
|
|
1291
|
+
if (error instanceof HttpFailure)
|
|
1292
|
+
return false;
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/** TAL-258: the session's WebUI record (absent for a CLI or gateway session) and the profile whose state.db holds its rows. */
|
|
1297
|
+
handoffTarget(sid) {
|
|
1298
|
+
let local = null;
|
|
1299
|
+
try {
|
|
1300
|
+
local = this.store.get(sid);
|
|
1301
|
+
}
|
|
1302
|
+
catch { /* a foreign session without a WebUI record */ }
|
|
1303
|
+
const profile = local?.profile ?? this.deps.activeProfile();
|
|
1304
|
+
return { local, profile, dbPath: this.stateDbPath(profile) };
|
|
1305
|
+
}
|
|
1306
|
+
/** Python `_handle_conversation_rounds` (TAL-258): the session's rounds and whether the handoff dock is due. */
|
|
1307
|
+
conversationRounds(sid, since) {
|
|
1308
|
+
const rounds = countConversationRounds(this.handoffTarget(sid).dbPath, sid, since);
|
|
1309
|
+
return { ok: true, rounds, threshold: CONVERSATION_ROUND_THRESHOLD, should_show: rounds >= CONVERSATION_ROUND_THRESHOLD };
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Python `_handle_handoff_summary` (TAL-258): the session's main model summarizes its last 50 state.db messages after
|
|
1313
|
+
* `since`, and the summary is appended as a display-only `handoff_summary` tool row. A failed or cut-off answer falls
|
|
1314
|
+
* back to a deterministic local summary rather than an error.
|
|
1315
|
+
*/
|
|
1316
|
+
async handoffSummary(sid, since) {
|
|
1317
|
+
this.rejectSubagent(sid, 'summarized');
|
|
1318
|
+
const { local, profile, dbPath } = this.handoffTarget(sid);
|
|
1319
|
+
const rounds = countConversationRounds(dbPath, sid, since);
|
|
1320
|
+
if (rounds < CONVERSATION_ROUND_THRESHOLD)
|
|
1321
|
+
throw new HttpFailure(400, 'Not enough conversation rounds to generate a summary.');
|
|
1322
|
+
// A running turn settles the transcript it started from, which would drop a card appended meanwhile.
|
|
1323
|
+
if (local?.active_stream_id)
|
|
1324
|
+
throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
|
|
1325
|
+
const all = stateDbSessionMessages(dbPath, sid, { stitch: true });
|
|
1326
|
+
const messages = (since === null ? all : all.filter((m) => { const ts = stateDbTimestampSeconds(m.timestamp); return ts !== null && ts > since; })).slice(-50);
|
|
1327
|
+
if (messages.length < 2)
|
|
1328
|
+
throw new HttpFailure(400, 'Not enough messages to summarize.');
|
|
1329
|
+
const sidecar = this.deps.sidecar?.() ?? null;
|
|
1330
|
+
await ensureAgentRuntimeCurrent(sidecar);
|
|
1331
|
+
const meta = local ? null : this.lookupCliMeta(sid);
|
|
1332
|
+
const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages);
|
|
1333
|
+
const pick = (row, ...keys) => { for (const key of keys) {
|
|
1334
|
+
const v = str(row?.[key]).trim();
|
|
1335
|
+
if (v)
|
|
1336
|
+
return v;
|
|
1337
|
+
} return ''; };
|
|
1338
|
+
// Python `get_cli_sessions` never listed WebUI's own state.db rows, so they name no channel.
|
|
1339
|
+
const cliRow = meta ?? this.lookupCliMeta(sid);
|
|
1340
|
+
const channel = pick(local, 'source_label', 'raw_source', 'source_tag', 'session_source') || (str(cliRow?.raw_source || cliRow?.source).trim().toLowerCase() === 'webui' ? '' : pick(cliRow, 'source_label', 'raw_source', 'source_tag', 'source')) || null;
|
|
1341
|
+
await this.persistHandoffMarker(sid, profile, handoffMarker(sid, generated.summary, channel, rounds, generated.fallback, this.deps.now()));
|
|
1342
|
+
return { ok: true, summary: generated.summary, message_count: messages.length, rounds, fallback: generated.fallback, ...(generated.warning ? { warning: generated.warning } : {}) };
|
|
1343
|
+
}
|
|
1344
|
+
/** The main model's summary at 700 tokens, once more at 1400 when it was cut off, else the local fallback. */
|
|
1345
|
+
async generateHandoffSummary(sidecar, profile, local, meta, messages) {
|
|
1346
|
+
const fallback = (warning) => ({ summary: fallbackHandoffSummary(messages), fallback: true, ...(warning ? { warning: `Summary generation used local fallback: ${warning}` } : {}) });
|
|
1347
|
+
if (!sidecar)
|
|
1348
|
+
return fallback('the Agent sidecar is not running');
|
|
1349
|
+
const [model, provider] = local ? this.deps.modelStateFromRequest(local.model, local.model_provider, local.model_provider) : [str(meta?.model).trim() || null, null];
|
|
1350
|
+
const prompt = [{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }];
|
|
1351
|
+
const ask = (maxTokens) => sidecar.call('aux.complete', { profile_home: this.deps.profileHome(profile), task: 'handoff_summary', messages: prompt, model: model ?? '', provider, max_tokens: maxTokens, temperature: 0.2 }, { timeoutMs: 60_000 });
|
|
1352
|
+
try {
|
|
1353
|
+
let result = await ask(700);
|
|
1354
|
+
if (completionIncomplete(result))
|
|
1355
|
+
result = await ask(1400);
|
|
1356
|
+
return completionIncomplete(result) ? fallback() : { summary: result.text, fallback: false };
|
|
1357
|
+
}
|
|
1358
|
+
catch (error) {
|
|
1359
|
+
if (error instanceof SidecarError && error.condition === 'agent_runtime_stale')
|
|
1360
|
+
throw staleRuntimeFailure(error);
|
|
1361
|
+
// No credential is not a failure: the predecessor answered with the local summary and no warning.
|
|
1362
|
+
if (error instanceof SidecarError && error.condition === 'credential_missing')
|
|
1363
|
+
return fallback();
|
|
1364
|
+
return fallback(sanitizePaths(error));
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Python `_persist_handoff_summary`: a messaging session gets the marker in state.db and its WebUI record; any other
|
|
1369
|
+
* session in its WebUI record, or in state.db when it has none. Each store skips a marker its tail already holds, and
|
|
1370
|
+
* the session lock keeps two summaries from both passing that check. A card neither store saved answers 503.
|
|
1371
|
+
*/
|
|
1372
|
+
async persistHandoffMarker(sid, profile, marker) {
|
|
1373
|
+
const card = messageHandoffPayload(marker);
|
|
1374
|
+
const toStateDb = async () => {
|
|
1375
|
+
if (sameHandoff(handoffPayload(stateDbTailToolContent(this.stateDbPath(profile), sid)), card))
|
|
1376
|
+
return true;
|
|
1377
|
+
const sidecar = this.deps.sidecar?.();
|
|
1378
|
+
if (!sidecar)
|
|
1379
|
+
return false;
|
|
1380
|
+
try {
|
|
1381
|
+
return (await sidecar.call('state_db.append_message', { profile_home: this.deps.profileHome(profile), session_id: sid, role: 'tool', content: str(marker.content), tool_name: 'handoff_summary', timestamp: Number(marker.timestamp) })).ok;
|
|
1382
|
+
}
|
|
1383
|
+
catch {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
await this.store.withLock(sid, async () => {
|
|
1388
|
+
let live = null;
|
|
1389
|
+
try {
|
|
1390
|
+
live = this.store.get(sid);
|
|
1391
|
+
}
|
|
1392
|
+
catch { /* no WebUI record */ }
|
|
1393
|
+
// A turn that started while the summary was generated would drop the card when it settles.
|
|
1394
|
+
if (live?.active_stream_id)
|
|
1395
|
+
throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
|
|
1396
|
+
const toLocal = () => {
|
|
1397
|
+
if (!live)
|
|
1398
|
+
return false;
|
|
1399
|
+
if (sameHandoff(messageHandoffPayload(live.messages.at(-1)), card))
|
|
1400
|
+
return true;
|
|
1401
|
+
live.messages.push(marker);
|
|
1402
|
+
this.store.save(live);
|
|
1403
|
+
return true;
|
|
1404
|
+
};
|
|
1405
|
+
const messaging = this.isMessagingSession(sid);
|
|
1406
|
+
const saved = messaging ? [await toStateDb(), toLocal()].some(Boolean) : toLocal() || await toStateDb();
|
|
1407
|
+
// The caller may switch away on success, so a card saved nowhere is an error, not a summary.
|
|
1408
|
+
if (!saved)
|
|
1409
|
+
throw new HttpFailure(503, 'The handoff summary could not be saved; please retry.');
|
|
1410
|
+
});
|
|
1122
1411
|
}
|
|
1123
1412
|
/** The session's compression job; a deleted session's finished result is dropped rather than served. */
|
|
1124
1413
|
compressionJob(sid) {
|
|
@@ -1397,6 +1686,87 @@ export class SessionService {
|
|
|
1397
1686
|
}
|
|
1398
1687
|
return { session_id: branch.session_id, title, parent_session_id: source.session_id };
|
|
1399
1688
|
}
|
|
1689
|
+
/** Python `read_session_lineage_report` on the active profile's state.db. */
|
|
1690
|
+
lineageReport(sid) {
|
|
1691
|
+
return stateDbLineageReport(this.stateDbPath(null), sid);
|
|
1692
|
+
}
|
|
1693
|
+
/** TAL-259: recovery reads the active profile's state.db, and a recovered sidecar joins that profile. */
|
|
1694
|
+
recoveryDeps() {
|
|
1695
|
+
return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: this.deps.recoveryStampingSince(), log: this.deps.log };
|
|
1696
|
+
}
|
|
1697
|
+
recoveryAudit() {
|
|
1698
|
+
return auditSessionRecovery(this.recoveryDeps());
|
|
1699
|
+
}
|
|
1700
|
+
recoveryRepairSafe() {
|
|
1701
|
+
return repairSafeSessionRecovery(this.recoveryDeps());
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Python `_handle_session_compression_recovery_start`: open a compression-exhausted session's focused continuation, an
|
|
1705
|
+
* empty fork keeping its workspace, model, profile, toolsets and worktree. A retry opens the oldest continuation
|
|
1706
|
+
* already started from it in the same profile; the source's lock makes concurrent starts converge on one child.
|
|
1707
|
+
*/
|
|
1708
|
+
async compressionRecoveryStart(sid) {
|
|
1709
|
+
if (this.isSubagentViewOnly(sid))
|
|
1710
|
+
throw new HttpFailure(400, 'Subagent sessions are view-only and cannot start compression recovery from WebUI');
|
|
1711
|
+
const action = COMPRESSION_RECOVERY_ACTION_START_FOCUSED;
|
|
1712
|
+
let created = false;
|
|
1713
|
+
const child = await this.store.withLock(sid, () => {
|
|
1714
|
+
// Validated under the lock: a delete that won it leaves no source to fork.
|
|
1715
|
+
const source = this.store.get(sid);
|
|
1716
|
+
if (!this.visibleToActiveProfile(source.profile))
|
|
1717
|
+
throw new HttpFailure(404, 'Session not found');
|
|
1718
|
+
if (!compressionRecoveryPayload(source))
|
|
1719
|
+
throw new HttpFailure(409, 'Session does not have a compression recovery action.');
|
|
1720
|
+
const existing = this.compressionRecoveryChild(sid, action, source.profile);
|
|
1721
|
+
if (existing)
|
|
1722
|
+
return existing;
|
|
1723
|
+
const base = (source.title || 'Untitled').trim() || 'Untitled';
|
|
1724
|
+
const fresh = new Session({
|
|
1725
|
+
title: base.endsWith(' (focused continuation)') ? base : `${base} (focused continuation)`, workspace: source.workspace, model: source.model, model_provider: source.model_provider,
|
|
1726
|
+
project_id: source.project_id, profile: source.profile, session_source: 'fork', personality: source.personality, enabled_toolsets: copyJson(source.enabled_toolsets),
|
|
1727
|
+
context_length: source.context_length, threshold_tokens: source.threshold_tokens, gateway_routing: copyJson(source.gateway_routing), gateway_routing_history: copyJson(source.gateway_routing_history),
|
|
1728
|
+
parent_session_id: source.session_id, worktree_path: source.worktree_path, worktree_branch: source.worktree_branch, worktree_repo_root: source.worktree_repo_root, worktree_created_at: source.worktree_created_at,
|
|
1729
|
+
compression_recovery_source_session_id: sid, compression_recovery_action: action,
|
|
1730
|
+
// An empty model-facing transcript: the focused follow-up must not replay the exhausted context.
|
|
1731
|
+
messages: [], context_messages: [], composer_draft: { text: '', files: [] },
|
|
1732
|
+
}, { workspace: source.workspace, model: source.model });
|
|
1733
|
+
try {
|
|
1734
|
+
this.store.save(fresh);
|
|
1735
|
+
}
|
|
1736
|
+
catch (error) {
|
|
1737
|
+
throw new HttpFailure(500, `Failed to start compression recovery: ${sanitizePaths(error)}`);
|
|
1738
|
+
}
|
|
1739
|
+
this.store.touch(fresh);
|
|
1740
|
+
created = true;
|
|
1741
|
+
return fresh;
|
|
1742
|
+
});
|
|
1743
|
+
if (created)
|
|
1744
|
+
this.publish('session_compression_recovery', child.profile, child.session_id);
|
|
1745
|
+
return {
|
|
1746
|
+
ok: true, session: this.publicSession(child), source_session_id: sid, recommended_recovery_action: action,
|
|
1747
|
+
message: created ? 'Started a focused continuation. Describe the next narrow task to continue.' : 'Opened the existing focused continuation for this exhausted session.',
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
/** Python `find_compression_recovery_session`: the oldest same-profile continuation started from `sid`, cached or persisted. */
|
|
1751
|
+
compressionRecoveryChild(sid, action, profile) {
|
|
1752
|
+
const matches = (s) => s.compression_recovery_source_session_id === sid && s.compression_recovery_action === action && this.deps.profilesMatch(s.profile, profile);
|
|
1753
|
+
const found = new Map();
|
|
1754
|
+
for (const s of this.store.sessions.values())
|
|
1755
|
+
if (matches(s))
|
|
1756
|
+
found.set(s.session_id, s);
|
|
1757
|
+
for (const id of this.store.persistedIds()) {
|
|
1758
|
+
if (found.has(id) || this.store.sessions.has(id))
|
|
1759
|
+
continue;
|
|
1760
|
+
try {
|
|
1761
|
+
const meta = this.store.get(id, { metadataOnly: true, promote: false, cacheOnMiss: false });
|
|
1762
|
+
if (matches(meta))
|
|
1763
|
+
found.set(id, meta);
|
|
1764
|
+
}
|
|
1765
|
+
catch { /* unreadable: not a match */ }
|
|
1766
|
+
}
|
|
1767
|
+
const [oldest] = [...found.values()].sort((a, b) => (a.created_at || 0) - (b.created_at || 0) || (a.updated_at || 0) - (b.updated_at || 0) || (a.session_id < b.session_id ? -1 : 1));
|
|
1768
|
+
return oldest ? this.store.get(oldest.session_id) : null;
|
|
1769
|
+
}
|
|
1400
1770
|
/** The run that keeps `delete` answering 409, if any; stream status reports it so a draining client can wait on it (TAL-622). */
|
|
1401
1771
|
activeRunBlocking(sid) {
|
|
1402
1772
|
const live = this.deps.runtime.activeRunStream(sid);
|
|
@@ -1416,8 +1786,9 @@ export class SessionService {
|
|
|
1416
1786
|
if (!isSafeSessionId(sid))
|
|
1417
1787
|
throw new HttpFailure(400, 'Invalid session_id');
|
|
1418
1788
|
// Python: a read-only import (persisted sidecar flag, sidebar metadata, or a foreign state.db owner such as a
|
|
1419
|
-
// Claude Code session) is never deleted — that would erase the owner's authoritative transcript.
|
|
1420
|
-
|
|
1789
|
+
// Claude Code session) is never deleted — that would erase the owner's authoritative transcript. An orphan has none.
|
|
1790
|
+
const orphaned = this.isOrphanedImport(sid);
|
|
1791
|
+
if (!orphaned && this.isReadOnlyImport(sid))
|
|
1421
1792
|
throw new HttpFailure(400, 'Read-only imported sessions cannot be deleted from WebUI');
|
|
1422
1793
|
if (this.isSubagentViewOnly(sid))
|
|
1423
1794
|
throw new HttpFailure(400, 'Subagent sessions are view-only and cannot be deleted from WebUI');
|
|
@@ -1437,11 +1808,7 @@ export class SessionService {
|
|
|
1437
1808
|
hadSidecar = false;
|
|
1438
1809
|
}
|
|
1439
1810
|
// Python `_is_messaging_session_id`: decided before the JSON is gone, from WebUI metadata or the Agent's row.
|
|
1440
|
-
const isMessaging = (
|
|
1441
|
-
if (isMessagingSessionRecord(this.store.get(sid, { metadataOnly: true }).compact()))
|
|
1442
|
-
return true;
|
|
1443
|
-
}
|
|
1444
|
-
catch { /* absent */ } const meta = this.lookupCliMeta(sid); return meta !== null && isMessagingSessionRecord(meta); })();
|
|
1811
|
+
const isMessaging = this.isMessagingSession(sid);
|
|
1445
1812
|
if (this.activeRunBlocking(sid))
|
|
1446
1813
|
throw new HttpFailure(409, 'Session has an active run; stop it before deleting');
|
|
1447
1814
|
try {
|
|
@@ -1479,9 +1846,16 @@ export class SessionService {
|
|
|
1479
1846
|
// Python: the Agent/CLI transcript in state.db goes too (else a claimable CLI row resurfaces in the sidebar), but a
|
|
1480
1847
|
// messaging channel's memory is never erased from the WebUI; the actual outcome is reported.
|
|
1481
1848
|
let stateDbCleanupFailed = false;
|
|
1482
|
-
if (!isMessaging) {
|
|
1849
|
+
if (!isMessaging && !orphaned) {
|
|
1483
1850
|
// TAL-529: the compression continuations go too, tip first; the Agent's delete keeps and detaches them otherwise.
|
|
1484
|
-
|
|
1851
|
+
let lineage = [];
|
|
1852
|
+
try {
|
|
1853
|
+
lineage = stateDbCompressionLineage(this.stateDbPath(eventProfile), sid);
|
|
1854
|
+
}
|
|
1855
|
+
catch {
|
|
1856
|
+
stateDbCleanupFailed = true;
|
|
1857
|
+
}
|
|
1858
|
+
for (const id of lineage.reverse()) {
|
|
1485
1859
|
try {
|
|
1486
1860
|
if (!(await this.deps.runtime.deleteCliSession(eventProfile, id)))
|
|
1487
1861
|
stateDbCleanupFailed = true;
|