@maudecode/talaria-web 1.19.0 → 1.20.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/automation-raw.js +61 -6
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +8 -6
- package/dist/api/automation-router.js +7 -1
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +258 -33
- package/dist/api/chat-router.js +60 -25
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +4 -0
- package/dist/api/git-router.js +1 -1
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.js +13 -1
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +2 -2
- package/dist/api/session-visibility.js +2 -2
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +787 -107
- package/dist/api/sessions-router.js +16 -4
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +34 -13
- package/dist/api/settings-router.js +54 -17
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +41 -19
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +314 -32
- package/dist/api/tools-router.js +135 -38
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.js +15 -11
- package/dist/app.js.map +1 -1
- package/dist/auth/crypto.d.ts +3 -0
- package/dist/auth/crypto.js +18 -0
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/gate.js +5 -1
- package/dist/auth/gate.js.map +1 -1
- package/dist/cli/ctl.js +6 -1
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/main.js +23 -18
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/supervise.d.ts +22 -0
- package/dist/cli/supervise.js +80 -2
- package/dist/cli/supervise.js.map +1 -1
- package/dist/config/agent-config.d.ts +3 -0
- package/dist/config/agent-config.js +36 -9
- package/dist/config/agent-config.js.map +1 -1
- package/dist/fs/atomic.d.ts +3 -0
- package/dist/fs/atomic.js +19 -5
- package/dist/fs/atomic.js.map +1 -1
- package/dist/fs/stat.d.ts +6 -0
- package/dist/fs/stat.js +18 -2
- package/dist/fs/stat.js.map +1 -1
- package/dist/http/context.d.ts +14 -0
- package/dist/http/context.js +48 -5
- package/dist/http/context.js.map +1 -1
- package/dist/profiles/profiles.d.ts +2 -0
- package/dist/profiles/profiles.js +1 -0
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +11 -6
- package/dist/providers/catalog.js +76 -24
- package/dist/providers/catalog.js.map +1 -1
- package/dist/redact.js +38 -14
- package/dist/redact.js.map +1 -1
- package/dist/release.js +5 -3
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +85 -21
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +24 -21
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +2 -1
- package/dist/sessions/anchor.js +5 -1
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +23 -3
- package/dist/sessions/background-tasks.js +74 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +9 -2
- package/dist/sessions/cli-sessions.js +38 -57
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +8 -0
- package/dist/sessions/completions.js +55 -25
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/export.d.ts +2 -0
- package/dist/sessions/export.js +6 -2
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +44 -4
- package/dist/sessions/gateway-sessions.js +626 -90
- package/dist/sessions/gateway-sessions.js.map +1 -1
- package/dist/sessions/gateway-watcher.d.ts +10 -11
- package/dist/sessions/gateway-watcher.js +25 -21
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/list.d.ts +9 -2
- package/dist/sessions/list.js +38 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +11 -5
- package/dist/sessions/merge.js +59 -17
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +7 -4
- package/dist/sessions/prefix-index.js +21 -10
- package/dist/sessions/prefix-index.js.map +1 -1
- package/dist/sessions/recovery.d.ts +1 -1
- package/dist/sessions/recovery.js +4 -3
- package/dist/sessions/recovery.js.map +1 -1
- package/dist/sessions/service.d.ts +107 -37
- package/dist/sessions/service.js +490 -156
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +2 -0
- package/dist/sessions/session.js +4 -0
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/state-db-worker.js +22 -5
- package/dist/sessions/state-db-worker.js.map +1 -1
- package/dist/sessions/state-db.d.ts +106 -15
- package/dist/sessions/state-db.js +651 -236
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/state-saved.d.ts +1 -1
- package/dist/sessions/state-saved.js +35 -11
- package/dist/sessions/state-saved.js.map +1 -1
- package/dist/sessions/storage/backend.d.ts +10 -1
- package/dist/sessions/storage/backend.js.map +1 -1
- package/dist/sessions/storage/conformance.js +1 -1
- package/dist/sessions/storage/conformance.js.map +1 -1
- package/dist/sessions/storage/json-documents.d.ts +2 -0
- package/dist/sessions/storage/json-documents.js +70 -26
- package/dist/sessions/storage/json-documents.js.map +1 -1
- package/dist/sessions/storage/json-files.d.ts +13 -2
- package/dist/sessions/storage/json-files.js +126 -6
- package/dist/sessions/storage/json-files.js.map +1 -1
- package/dist/sessions/storage/json-journal.d.ts +6 -7
- package/dist/sessions/storage/json-journal.js +143 -73
- package/dist/sessions/storage/json-journal.js.map +1 -1
- package/dist/sessions/storage/json.d.ts +15 -1
- package/dist/sessions/storage/json.js +125 -33
- package/dist/sessions/storage/json.js.map +1 -1
- package/dist/sessions/store.d.ts +5 -1
- package/dist/sessions/store.js +67 -27
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +8 -7
- package/dist/sessions/streams.js +49 -14
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +3 -1
- package/dist/sessions/tool-display.js +6 -0
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.js +6 -2
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +23 -5
- package/dist/sessions/turn.js +168 -78
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sidecar/client.d.ts +24 -1
- package/dist/sidecar/client.js +118 -38
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/fake.d.ts +6 -2
- package/dist/sidecar/fake.js +15 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.d.ts +9 -4
- package/dist/spa.js +36 -12
- package/dist/spa.js.map +1 -1
- package/dist/startup-timing.d.ts +66 -0
- package/dist/startup-timing.js +129 -0
- package/dist/startup-timing.js.map +1 -0
- package/dist/test/fs-handles.d.ts +11 -0
- package/dist/test/fs-handles.js +31 -0
- package/dist/test/fs-handles.js.map +1 -0
- package/dist/tools/connectors.d.ts +1 -1
- package/dist/tools/connectors.js +2 -2
- package/dist/tools/connectors.js.map +1 -1
- package/dist/tools/crons.d.ts +8 -4
- package/dist/tools/crons.js +25 -18
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +29 -0
- package/dist/tools/dashboard-plugins.js +132 -0
- package/dist/tools/dashboard-plugins.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +25 -5
- package/dist/tools/gateway-restart.js +103 -23
- package/dist/tools/gateway-restart.js.map +1 -1
- package/dist/tools/health.d.ts +13 -1
- package/dist/tools/health.js +80 -22
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/insights.d.ts +1 -1
- package/dist/tools/insights.js +2 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/mcp-health.js +74 -24
- package/dist/tools/mcp-health.js.map +1 -1
- package/dist/tools/mcp.js +19 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/plugin-oauth.d.ts +14 -0
- package/dist/tools/plugin-oauth.js +53 -0
- package/dist/tools/plugin-oauth.js.map +1 -0
- package/dist/tools/prune-advice.d.ts +50 -0
- package/dist/tools/prune-advice.js +114 -0
- package/dist/tools/prune-advice.js.map +1 -0
- package/dist/tools/skills.d.ts +5 -2
- package/dist/tools/skills.js +9 -6
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +73 -7
- package/dist/tools/update-notifications.js +277 -43
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +8 -1
- package/dist/tools/updates.js +51 -17
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/media.js +69 -20
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.js +4 -14
- package/dist/workspace/preview.js.map +1 -1
- package/dist/workspace/rollback.d.ts +6 -2
- package/dist/workspace/rollback.js +17 -7
- package/dist/workspace/rollback.js.map +1 -1
- package/package.json +3 -3
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/agent_support.json +17 -14
- package/sidecar/scripts/suite_runner.py +183 -27
- package/sidecar/scripts/test.sh +5 -7
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +10 -3
- package/sidecar/talaria_sidecar/agent_mutation.py +159 -23
- package/sidecar/talaria_sidecar/agent_services.py +74 -11
- package/sidecar/talaria_sidecar/context_detail.py +59 -0
- package/sidecar/talaria_sidecar/home.py +97 -20
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +80 -18
- package/sidecar/talaria_sidecar/methods/chat.py +571 -106
- package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
- package/sidecar/talaria_sidecar/methods/commands.py +277 -16
- package/sidecar/talaria_sidecar/methods/config.py +73 -3
- package/sidecar/talaria_sidecar/methods/connectors.py +45 -13
- package/sidecar/talaria_sidecar/methods/cron.py +8 -2
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +24 -4
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +211 -27
- package/sidecar/talaria_sidecar/methods/kanban.py +2 -3
- package/sidecar/talaria_sidecar/methods/oauth.py +139 -11
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
- package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
- package/sidecar/talaria_sidecar/methods/process.py +70 -6
- package/sidecar/talaria_sidecar/methods/profiles.py +4 -3
- package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
- package/sidecar/talaria_sidecar/methods/skills.py +22 -7
- package/sidecar/talaria_sidecar/methods/state_db.py +128 -42
- package/sidecar/talaria_sidecar/methods/toolsets.py +4 -2
- package/sidecar/talaria_sidecar/methods/worktree.py +121 -2
- package/sidecar/talaria_sidecar/rpc.py +25 -3
- package/sidecar/talaria_sidecar/runtime.py +42 -3
- package/sidecar/talaria_sidecar/speed.py +13 -1
- package/sidecar/talaria_sidecar/startup_timing.py +119 -0
- package/static/dist/FILES.txt +241 -213
- package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
- package/static/dist/assets/AppShell-3isgvoro.js +8 -0
- package/static/dist/assets/Button-f4n6dapf.js +1 -0
- package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
- package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
- package/static/dist/assets/Dialog-hta5utr3.js +1 -0
- package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
- package/static/dist/assets/Field-b6ez48oz.js +1 -0
- package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
- package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
- package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
- package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
- package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
- package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
- package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
- package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
- package/static/dist/assets/Select-kr07928w.js +1 -0
- package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
- package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
- package/static/dist/assets/States-icab19yg.js +1 -0
- package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
- package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
- package/static/dist/assets/_app-ga1moszl.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
- package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
- package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
- package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
- package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
- package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
- package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
- package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
- package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
- package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
- package/static/dist/assets/_app.skills-n016rihd.js +1 -0
- package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
- package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
- package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
- package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
- package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
- package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
- package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
- package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
- package/static/dist/assets/archive-il7c6053.js +1 -0
- package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
- package/static/dist/assets/back-gu3nu279.js +1 -0
- package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-joedghi3.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
- package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-hyrjrohe.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
- package/static/dist/assets/cancel-bsnfo36u.js +1 -0
- package/static/dist/assets/channel-dsbtk01y.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-koouqbut.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
- package/static/dist/assets/{chunk-75Z2AOVW-bl4bhdnt.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
- package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
- package/static/dist/assets/{chunk-F27PBJKO-d8xxi0zi.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
- package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
- package/static/dist/assets/{chunk-GMAD6QVW-jro0hl73.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
- package/static/dist/assets/{chunk-GVQU2GXP-djn6gbyu.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-vw4p5p7o.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
- package/static/dist/assets/{chunk-L3NEJ4N5-n1hj77q8.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
- package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
- package/static/dist/assets/{chunk-P2QGCYS3-onf1hz6c.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-m8sqn7cs.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ek4smc8x.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-g9gfvefo.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
- package/static/dist/assets/{chunk-SVP7TREG-f9nfv2cu.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
- package/static/dist/assets/{chunk-TICWLB2K-bnk3m1mq.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
- package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
- package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
- package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
- package/static/dist/assets/copied-bz3gkazh.js +1 -0
- package/static/dist/assets/copy-jo006coq.js +1 -0
- package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
- package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
- package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
- package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
- package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
- package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
- package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
- package/static/dist/assets/dagre-o5jmjz46.js +1 -0
- package/static/dist/assets/{diagram-S7CK7UJ4-m6aeoe7c.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
- package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
- package/static/dist/assets/{diagram-VSXAHHWV-hbawi7hn.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
- package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
- package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
- package/static/dist/assets/dist-crc46di9.js +7 -0
- package/static/dist/assets/dist-etpoyscq.js +44 -0
- package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
- package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
- package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
- package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
- package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
- package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
- package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ma6jxj5i.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
- package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-hzltvsod.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
- package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
- package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
- package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
- package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
- package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
- package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
- package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
- package/static/dist/assets/host-fr6lni1p.js +1 -0
- package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
- package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
- package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
- package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
- package/static/dist/assets/index-73649w7o.js +10 -0
- package/static/dist/assets/index-lhf1fsdf.css +2 -0
- package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
- package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-e7bnwq7h.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
- package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
- package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
- package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
- package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-etd1bi6p.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
- package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
- package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
- package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
- package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
- package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-emb1g9dz.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
- package/static/dist/assets/katex-cbdd1ohw.css +1 -0
- package/static/dist/assets/katex-pbij2oo6.js +257 -0
- package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
- package/static/dist/assets/{line-h0ojzsvs.js → line-mtr5k6il.js} +1 -1
- package/static/dist/assets/linear-jlwrvvy6.js +1 -0
- package/static/dist/assets/link-iadw4ghw.js +2 -0
- package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
- package/static/dist/assets/locale-cs-n41hptse.js +5 -0
- package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
- package/static/dist/assets/locale-es-ih078lco.js +5 -0
- package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
- package/static/dist/assets/locale-it-kb96jufy.js +5 -0
- package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
- package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
- package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
- package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
- package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
- package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
- package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
- package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
- package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
- package/static/dist/assets/login-lkhtzfcl.js +1 -0
- package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
- package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
- package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
- package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
- package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
- package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-o1vathns.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
- package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
- package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
- package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
- package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
- package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
- package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
- package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
- package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
- package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
- package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
- package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-owqc4uqn.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
- package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
- package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
- package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
- package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
- package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
- package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
- package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
- package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
- package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
- package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
- package/static/dist/assets/registry-g7w3353u.js +1 -0
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
- package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
- package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
- package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
- package/static/dist/assets/runtime-nlxpnegr.js +1 -0
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-iqc02emv.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
- package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
- package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-i0v116q0.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
- package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
- package/static/dist/assets/share._token-o76ki0ly.js +2 -0
- package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
- package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
- package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
- package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
- package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
- package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
- package/static/dist/assets/src-h89s0hs4.js +1 -0
- package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
- package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
- package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
- package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
- package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
- package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
- package/static/dist/assets/{timeline-definition-24CTP7MA-ol2hnvma.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
- package/static/dist/assets/toast-liparz2j.js +56 -0
- package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
- package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
- package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
- package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
- package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
- package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
- package/static/dist/assets/untitled-mj86r4gz.js +1 -0
- package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
- package/static/dist/assets/useForm-o4uu74mx.js +1 -0
- package/static/dist/assets/{useParams-gjc70qru.js → useParams-ftsveu84.js} +1 -1
- package/static/dist/assets/{useSearch-e6ae539u.js → useSearch-crqueqga.js} +1 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
- package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
- package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
- package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
- package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
- package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
- package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
- package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-aqbgvujg.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
- package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
- package/static/dist/extension-sdk.js +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +180 -136
- package/static/dist/assets/AppShell-cey33v9s.js +0 -8
- package/static/dist/assets/ChatPage-i8wnugr7.js +0 -18
- package/static/dist/assets/ConnectTargets-hg19553x.js +0 -1
- package/static/dist/assets/ExtensionRoute-cly4apm5.js +0 -1
- package/static/dist/assets/Field-j8uqoigz.js +0 -9
- package/static/dist/assets/HubRoute-e4bgz41j.js +0 -2
- package/static/dist/assets/Markdown-lahuichm.js +0 -598
- package/static/dist/assets/Markdown-lwviulmg.css +0 -1
- package/static/dist/assets/RightPanel-lbo4i0fn.js +0 -1
- package/static/dist/assets/Select-gqatm2e0.js +0 -1
- package/static/dist/assets/SessionListPanel-mfqayfjh.js +0 -1
- package/static/dist/assets/SettingsLayout-b8980u7k.js +0 -1
- package/static/dist/assets/States-e7dkgtxf.js +0 -1
- package/static/dist/assets/Toaster-bfqqy786.js +0 -1
- package/static/dist/assets/_app-n1n6pfx1.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-buxjxav3.js +0 -1
- package/static/dist/assets/_app.index-jt8zwiu2.js +0 -1
- package/static/dist/assets/_app.insights-cv76ml71.js +0 -1
- package/static/dist/assets/_app.kanban-h6ts53j7.js +0 -1
- package/static/dist/assets/_app.logs-dm873z90.js +0 -1
- package/static/dist/assets/_app.memory-glk5y6m8.js +0 -1
- package/static/dist/assets/_app.profiles-ka6zpstq.js +0 -1
- package/static/dist/assets/_app.session._sessionId-g2vzghvc.js +0 -1
- package/static/dist/assets/_app.settings-hgjdhzcd.js +0 -1
- package/static/dist/assets/_app.settings._section-jf2kmhae.js +0 -3
- package/static/dist/assets/_app.skills-cii852yq.js +0 -1
- package/static/dist/assets/_app.tasks-fp9q7atm.js +0 -1
- package/static/dist/assets/_app.todos-dw94qzue.js +0 -1
- package/static/dist/assets/_app.workspaces-inyygzze.js +0 -1
- package/static/dist/assets/abnfDiagram-VCTEODGH-gjayq3ra.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-nwb3xovn.js +0 -36
- package/static/dist/assets/archive-b1j004vn.js +0 -1
- package/static/dist/assets/back-g74gkozu.js +0 -1
- package/static/dist/assets/channel-es95o4y8.js +0 -1
- package/static/dist/assets/chunk-DU6HZSFF-gpedttsp.js +0 -128
- package/static/dist/assets/chunk-OSK3NFVY-oy0gwn2n.js +0 -10
- package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +0 -1
- package/static/dist/assets/copied-bli9tbcx.js +0 -1
- package/static/dist/assets/copy-jamtd1pf.js +0 -1
- package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
- package/static/dist/assets/cynefin-OW5HDTMX-f8xfdd3f.js +0 -1
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-n9lb99h9.js +0 -62
- package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
- package/static/dist/assets/dagre-GXQ25YYZ-dgailddi.js +0 -4
- package/static/dist/assets/dagre-lkoknbjn.js +0 -1
- package/static/dist/assets/diagram-UQ7AKVKN-jall3j0x.js +0 -41
- package/static/dist/assets/diagram-VX7I27RA-hendl2tt.js +0 -24
- package/static/dist/assets/diagram-Z3DM3KII-igwc35p3.js +0 -24
- package/static/dist/assets/ebnfDiagram-PWID7BFC-c64knz08.js +0 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-brdu9lcm.js +0 -99
- package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +0 -1
- package/static/dist/assets/folder-plus-lvt9kudb.js +0 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-naa9w6xa.js +0 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-ghoavj8d.js +0 -1
- package/static/dist/assets/host-hzm8n85n.js +0 -1
- package/static/dist/assets/index-drdcaqeq.css +0 -2
- package/static/dist/assets/index-jzm844he.js +0 -11
- package/static/dist/assets/info-A6RAGUB7-mn2ut5ko.js +0 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-h6kuqvsb.js +0 -2
- package/static/dist/assets/lazyRouteComponent-o816ytzl.js +0 -1
- package/static/dist/assets/linear-becwlnhk.js +0 -1
- package/static/dist/assets/link-k01ryaf9.js +0 -1
- package/static/dist/assets/login-bsae49ew.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +0 -1
- package/static/dist/assets/mermaid-parser.core-bnre27u8.js +0 -7
- package/static/dist/assets/not-found-ccnnac39.js +0 -1
- package/static/dist/assets/onboarding-c65tysya.js +0 -1
- package/static/dist/assets/onboarding_model_label-gpn28uix.js +0 -1
- package/static/dist/assets/packet-AYTQ26CC-hatq7m2e.js +0 -1
- package/static/dist/assets/pegDiagram-XKGWAZYB-gwisz8jv.js +0 -1
- package/static/dist/assets/pie-WAS4IAKB-fpftf0mk.js +0 -1
- package/static/dist/assets/pieDiagram-E7YTZNPT-khm1o9a6.js +0 -39
- package/static/dist/assets/radar-RG4KPBEZ-cntfwjpq.js +0 -1
- package/static/dist/assets/railroad-74A4TZTK-a7t5eibp.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +0 -1
- package/static/dist/assets/railroadDiagram-O6MQD6OU-etuh0qa0.js +0 -1
- package/static/dist/assets/refresh-cw-mqbkv2a3.js +0 -1
- package/static/dist/assets/refresh-l29ljp76.js +0 -1
- package/static/dist/assets/requirementDiagram-BXWQKSXE-oq2ulk22.js +0 -84
- package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
- package/static/dist/assets/root-mbklpuv4.js +0 -1
- package/static/dist/assets/saved-ndd4t2xb.js +0 -1
- package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +0 -1
- package/static/dist/assets/share._token-c1ri0hy1.js +0 -2
- package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
- package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +0 -1
- package/static/dist/assets/src-hjyib4mu.js +0 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-dnpt1ato.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +0 -1
- package/static/dist/assets/swimlanes-42K2YHIH-loqbdazb.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +0 -8
- package/static/dist/assets/toast-i3h34v6o.js +0 -64
- package/static/dist/assets/treeView-Q6P3EWNA-idrj6iox.js +0 -1
- package/static/dist/assets/treemap-WGGIJYW6-lzr49vv5.js +0 -1
- package/static/dist/assets/untitled-oaoya4qj.js +0 -1
- package/static/dist/assets/useForm-nfaxgmsk.js +0 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-bvwr9o2z.js +0 -34
- package/static/dist/assets/wardley-WFR3VGLG-31ui8tma.js +0 -1
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-k5wqtx4b.js +0 -78
- /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
package/dist/sessions/service.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unavailableContextDetail } from '@maudecode/talaria-web-contracts';
|
|
1
2
|
import { filterAsync } from '../util.js';
|
|
2
3
|
import { statSignature } from '../fs/stat.js';
|
|
3
4
|
import { SessionPrefixIndex } from './prefix-index.js';
|
|
@@ -8,16 +9,16 @@ import { buildActiveTurnToken, copyJson, redactSessionData, redactValue, stripPu
|
|
|
8
9
|
import { DraftVersionConflict, normalizeDraftVersion } from './drafts.js';
|
|
9
10
|
import { allSessions, buildSessionListPayload, reconcileLocalSessionRows, sessionLineageIds, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, ownerLockCandidates, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchPreview, sessionSearchTerms } from './list.js';
|
|
10
11
|
import { anchorSceneIntOrNull, fullToolResult, hydrateAnchorActivityScenes, normalizeAnchorSceneMessageRef, readAnchorSceneRows, storeAnchorScene, withTurnIds } from './anchor.js';
|
|
11
|
-
import { contextUsage, COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, forgetContextFigures, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
|
|
12
|
+
import { contextUsage, COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, forgetContextFigures, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom, WORKTREE_RETAINED_KEY } from './session.js';
|
|
12
13
|
import { SessionBusy, SessionNotFound } from './store.js';
|
|
13
14
|
import { UNSETTLED_TODO_KEY, attachTodoSnapshot, attachTodoState } from './todo.js';
|
|
14
15
|
import { isClaudeCodeSessionId } from './claude-code.js';
|
|
15
16
|
import { auditSessionRecovery, repairSafeSessionRecovery } from './recovery.js';
|
|
16
|
-
import {
|
|
17
|
+
import { stateDbWebDiagnosticIdsAsync, agentSessionRowsExistingAsync, CANONICAL_READ_ONLY_REASON, OWNERSHIP_UNAVAILABLE_REASON, CONVERSATION_ROUND_THRESHOLD, emptyLineageReport, stateDbSessionReadAsync, stateDbSessionSourcesAsync, stateDbTimestampSeconds, withStateDbListingReads } from './state-db.js';
|
|
17
18
|
import { completionIncomplete, fallbackHandoffSummary, HANDOFF_SYSTEM_PROMPT, handoffMarker, handoffPayload, handoffTranscript, messageHandoffPayload, sameHandoff } from './handoff.js';
|
|
18
19
|
import { anchorMessageKey, anchorSummary, CompressionJobs, compressionReference, visibleMessagesForAnchor } from './compress.js';
|
|
19
20
|
import { chatReadiness, runtimeFailureDetails, SidecarError } from '../sidecar/client.js';
|
|
20
|
-
import { agentSteerText, ApiMessageSummary, appendStateDbIdentityRows, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, newestSettledReply, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withMirrorSources, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
|
|
21
|
+
import { agentSteerText, ApiMessageSummary, appendStateDbIdentityRows, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, withDataUseRequirements, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, newestSettledReply, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withMirrorSources, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
|
|
21
22
|
import { withBackgroundUpdates } from './background-updates.js';
|
|
22
23
|
import { markedStateDbGeneration, stateDbIdentityKeys, stampStateDbGeneration, stateDbMessageRepresented } from './state-db-identity.js';
|
|
23
24
|
import { sourceKind } from './source-kind.js';
|
|
@@ -42,11 +43,16 @@ export class HttpFailure extends Error {
|
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Python `_agent_runtime_barrier_response`: a stale local Agent checkout is refused with a typed 409 before the caller
|
|
45
|
-
* mutates anything.
|
|
46
|
+
* mutates anything. An unavailable connection is refused before dispatch; other check failures are not a verdict
|
|
47
|
+
* and let the caller proceed.
|
|
46
48
|
*/
|
|
47
49
|
export async function ensureAgentRuntimeCurrent(sidecar) {
|
|
48
50
|
if (!sidecar)
|
|
49
51
|
return;
|
|
52
|
+
// Runtime RPCs bypass the client's ready gate and can queue behind an unfinished handshake.
|
|
53
|
+
if (chatReadiness(sidecar).condition === 'sidecar_unavailable') {
|
|
54
|
+
throw new HttpFailure(503, 'The Agent sidecar is not ready; retry once it finishes starting.', { condition: 'sidecar_unavailable' });
|
|
55
|
+
}
|
|
50
56
|
try {
|
|
51
57
|
await sidecar.call('runtime.ensure_current', {});
|
|
52
58
|
}
|
|
@@ -57,6 +63,8 @@ export async function ensureAgentRuntimeCurrent(sidecar) {
|
|
|
57
63
|
}
|
|
58
64
|
/** The health probe is also the browser's server-reachability probe (30 s), so the readiness check answers well inside it. */
|
|
59
65
|
const CHAT_READINESS_CHECK_TIMEOUT_MS = 5_000;
|
|
66
|
+
/** TAL-1254: how soon a worktree lock the sidecar could not confirm is tried again. */
|
|
67
|
+
export const WORKTREE_RETAIN_RETRY_MS = 60_000;
|
|
60
68
|
/**
|
|
61
69
|
* TAL-951: whether a chat turn would be admitted now. Ready and source-stale incompatible sidecars both run turn
|
|
62
70
|
* admission's live check: recovery state can change after the handshake. A check
|
|
@@ -92,6 +100,8 @@ export class SessionService {
|
|
|
92
100
|
compressionJobs;
|
|
93
101
|
/** TAL-1005: the turns now waiting on their session's compression job before they read its history. */
|
|
94
102
|
compactionWaiters = new Set();
|
|
103
|
+
worktreeRetainRetry = null;
|
|
104
|
+
worktreesUnconfirmed = 0;
|
|
95
105
|
constructor(deps) {
|
|
96
106
|
this.deps = deps;
|
|
97
107
|
this.compressionJobs = new CompressionJobs();
|
|
@@ -121,7 +131,7 @@ export class SessionService {
|
|
|
121
131
|
throw new HttpFailure(400, error.message);
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
|
-
/**
|
|
134
|
+
/** Split qualified ids and opportunistically repair against fresh evidence; missing discovery never delays a turn. */
|
|
125
135
|
turnModel(s, body = {}) {
|
|
126
136
|
const requestedModel = str(body.model) || s.model;
|
|
127
137
|
const requestedProvider = 'model_provider' in body ? body.model_provider : s.model_provider;
|
|
@@ -190,87 +200,104 @@ export class SessionService {
|
|
|
190
200
|
return this.visibleToActiveProfile(session.profile);
|
|
191
201
|
}
|
|
192
202
|
localTargets = null;
|
|
193
|
-
/** One receipt-backed physical target for every operation on a local conversation alias. */
|
|
194
|
-
resolveSessionId(sid) {
|
|
203
|
+
/** One receipt-backed physical target for every operation on a local conversation alias, read on the state.db worker. */
|
|
204
|
+
async resolveSessionId(sid) {
|
|
195
205
|
try {
|
|
196
206
|
const path = this.stateDbPath(null);
|
|
197
|
-
const
|
|
207
|
+
const stamp = () => JSON.stringify([statSignature(path), statSignature(`${path}-wal`), path]);
|
|
208
|
+
const key = stamp();
|
|
198
209
|
if (this.localTargets?.key !== key)
|
|
199
210
|
this.localTargets = { key, ids: new Map() };
|
|
200
211
|
const cached = this.localTargets.ids.get(sid);
|
|
201
212
|
if (cached)
|
|
202
213
|
return cached;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
214
|
+
const { target, read } = await stateDbSessionReadAsync({ target: sid, read: false }, 'stateDbLocalTargetRead', path, sid);
|
|
215
|
+
// A failed or changing read cannot seed a routing cache.
|
|
216
|
+
const targets = this.localTargets;
|
|
217
|
+
if (read && targets.key === key && stamp() === key) {
|
|
218
|
+
if (targets.ids.size >= 500)
|
|
219
|
+
targets.ids.clear();
|
|
220
|
+
targets.ids.set(sid, target);
|
|
221
|
+
}
|
|
222
|
+
return target;
|
|
211
223
|
}
|
|
212
224
|
catch {
|
|
213
225
|
return sid;
|
|
214
226
|
}
|
|
215
227
|
}
|
|
216
228
|
/** Python `_lookup_cli_session_metadata(...).get('read_only')` plus the persisted sidecar flag and a not-claimable foreign owner. */
|
|
217
|
-
async isReadOnlyImport(sid) {
|
|
229
|
+
async isReadOnlyImport(sid, reads = this.sessionGateReads(sid)) {
|
|
218
230
|
try {
|
|
219
231
|
const s = (await this.store.get(sid, { metadataOnly: true }));
|
|
220
|
-
if (
|
|
232
|
+
if (await this.loadedReadOnlyReason(s, undefined, reads))
|
|
221
233
|
return true;
|
|
222
234
|
}
|
|
223
235
|
catch {
|
|
224
236
|
// No sidecar: a foreign state.db transcript whose owner refuses claiming is read-only too.
|
|
225
|
-
const { session, reason } = (await this.claimOrSynthesizeCliSession(sid));
|
|
237
|
+
const { session, reason } = (await this.claimOrSynthesizeCliSession(sid, undefined, reads));
|
|
226
238
|
if (session && reason === 'not_claimable')
|
|
227
239
|
return true;
|
|
228
240
|
}
|
|
229
|
-
const meta =
|
|
241
|
+
const meta = await reads.meta();
|
|
230
242
|
return meta !== null && Boolean(meta.read_only);
|
|
231
243
|
}
|
|
232
244
|
/** Python `_session_is_subagent_view_only`: a delegated child by any signal — the persisted sidecar or the state.db row. */
|
|
233
|
-
async isSubagentViewOnly(sid) {
|
|
245
|
+
async isSubagentViewOnly(sid, reads = this.sessionGateReads(sid)) {
|
|
246
|
+
let profile = this.deps.activeProfile();
|
|
234
247
|
try {
|
|
235
248
|
const s = (await this.store.get(sid, { metadataOnly: true, cacheOnMiss: false, promote: false }));
|
|
249
|
+
profile = s.profile || 'default';
|
|
236
250
|
if (str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() === 'subagent')
|
|
237
251
|
return true;
|
|
238
252
|
}
|
|
239
253
|
catch { /* no sidecar */ }
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
// Admission needs authority, not the sidebar's visibility, message counts or enrichment.
|
|
255
|
+
const owners = await reads.owners(profile);
|
|
256
|
+
if (!owners)
|
|
257
|
+
throw new HttpFailure(403, OWNERSHIP_UNAVAILABLE_REASON);
|
|
258
|
+
const owner = owners.get(sid);
|
|
259
|
+
return str(typeof owner === 'object' ? owner.source : owner).trim().toLowerCase() === 'subagent';
|
|
260
|
+
}
|
|
261
|
+
/** TAL-358: batched `sessions.source` owners from the active profile's state.db, read on the state.db worker (TAL-923). */
|
|
262
|
+
stateDbSources = async (ids, profile = this.deps.activeProfile()) => {
|
|
245
263
|
try {
|
|
246
|
-
return
|
|
264
|
+
return await stateDbSessionSourcesAsync(this.stateDbPath(profile), ids);
|
|
247
265
|
}
|
|
248
266
|
catch {
|
|
249
267
|
return null;
|
|
250
268
|
}
|
|
251
269
|
};
|
|
270
|
+
/** The owners of `byProfile`'s ids, read up front, as the synchronous lookup `withOwnerLocks` takes. */
|
|
271
|
+
async ownerSources(byProfile, read = this.stateDbSources) {
|
|
272
|
+
const owners = new Map(await Promise.all([...byProfile].map(async ([profile, ids]) => [profile, ids.size ? await read([...ids], profile) : new Map()])));
|
|
273
|
+
return (_ids, profile = this.deps.activeProfile()) => owners.get(profile) ?? null;
|
|
274
|
+
}
|
|
275
|
+
/** `withOwnerLocks` with the owners read on the state.db worker, so a gate or payload never opens state.db on this thread. */
|
|
276
|
+
async ownerLocked(rows, read) {
|
|
277
|
+
return withOwnerLocks(rows, await this.ownerSources(ownerLockCandidates(rows), read));
|
|
278
|
+
}
|
|
252
279
|
/**
|
|
253
280
|
* TAL-590 (Python #3238): the read-only CLI or API-server imports among `rows` whose active-profile state.db row is
|
|
254
281
|
* gone, so their source was deleted outside Web and no Web action could remove them. An unreadable state.db proves
|
|
255
282
|
* nothing (`agentSessionRowsExisting`); another profile's session or one with a run the runtime still holds is never
|
|
256
283
|
* an orphan, while a stream id a crash left behind does not count as one.
|
|
257
284
|
*/
|
|
258
|
-
async orphanedImports(rows, existing, owners) {
|
|
285
|
+
async orphanedImports(rows, existing, owners, reads) {
|
|
259
286
|
const candidates = (await this.orphanCandidates(rows));
|
|
260
287
|
if (!candidates.length)
|
|
261
288
|
return new Set();
|
|
262
|
-
existing ??=
|
|
289
|
+
existing ??= await (reads ? reads.existing() : agentSessionRowsExistingAsync(this.stateDbPath(null), candidates));
|
|
263
290
|
if (owners === undefined)
|
|
264
|
-
owners = this.stateDbSources(candidates);
|
|
291
|
+
owners = await (reads ? reads.owners() : this.stateDbSources(candidates));
|
|
265
292
|
return new Set(candidates.filter((sid) => !existing.has(sid) && owners !== null && typeof owners.get(sid) !== 'object'));
|
|
266
293
|
}
|
|
267
294
|
async orphanCandidates(rows) {
|
|
268
295
|
return (await filterAsync(rows, async (r) => r.read_only && ['cli', 'api'].includes(sourceKind(r)) && this.visibleToActiveProfile(str(r.profile) || null) && !(await this.activeRunBlocking(str(r.session_id)))))
|
|
269
296
|
.map((r) => str(r.session_id));
|
|
270
297
|
}
|
|
271
|
-
async isOrphanedImport(sid) {
|
|
298
|
+
async isOrphanedImport(sid, reads = this.sessionGateReads(sid)) {
|
|
272
299
|
try {
|
|
273
|
-
return (await this.orphanedImports([(await this.store.get(sid, { metadataOnly: true })).compact()])).size > 0;
|
|
300
|
+
return (await this.orphanedImports([(await this.store.get(sid, { metadataOnly: true })).compact()], undefined, undefined, reads)).size > 0;
|
|
274
301
|
}
|
|
275
302
|
catch {
|
|
276
303
|
return false;
|
|
@@ -288,14 +315,14 @@ export class SessionService {
|
|
|
288
315
|
for (const id of cliRowIds)
|
|
289
316
|
activeIds.add(id);
|
|
290
317
|
byProfile.set(activeProfile, activeIds);
|
|
291
|
-
const owners =
|
|
318
|
+
const owners = await this.ownerSources(byProfile);
|
|
292
319
|
const checked = new Set((await this.orphanCandidates(rows)));
|
|
293
320
|
const existing = checked.size ? await agentSessionRowsExistingAsync(dbPath, checked) : new Set();
|
|
294
321
|
return {
|
|
295
|
-
stateDbSources:
|
|
322
|
+
stateDbSources: owners,
|
|
296
323
|
pruneOrphanedImports: async (current) => {
|
|
297
324
|
// Only an id the worker checked can be an orphan; a row that became a candidate meanwhile waits for the next list.
|
|
298
|
-
const orphans = (await this.orphanedImports(current.filter((r) => checked.has(str(r.session_id))), existing, owners
|
|
325
|
+
const orphans = (await this.orphanedImports(current.filter((r) => checked.has(str(r.session_id))), existing, owners([], activeProfile)));
|
|
299
326
|
for (const sid of orphans)
|
|
300
327
|
(await this.store.delete(sid));
|
|
301
328
|
return orphans;
|
|
@@ -303,7 +330,7 @@ export class SessionService {
|
|
|
303
330
|
};
|
|
304
331
|
}
|
|
305
332
|
/** The persisted flag folded with the state.db owner lock (TAL-358): the one read-only rule for mutation gates and the wire. */
|
|
306
|
-
async readOnlyReason(s, profile) {
|
|
333
|
+
async readOnlyReason(s, profile, reads) {
|
|
307
334
|
if (typeof s === 'string') {
|
|
308
335
|
try {
|
|
309
336
|
s = await this.store.get(s, { metadataOnly: true, cacheOnMiss: false, promote: false });
|
|
@@ -313,22 +340,25 @@ export class SessionService {
|
|
|
313
340
|
return OWNERSHIP_UNAVAILABLE_REASON;
|
|
314
341
|
}
|
|
315
342
|
}
|
|
316
|
-
return this.loadedReadOnlyReason(s, profile);
|
|
343
|
+
return await this.loadedReadOnlyReason(s, profile, reads);
|
|
317
344
|
}
|
|
318
|
-
loadedReadOnlyReason(s, profile) {
|
|
345
|
+
async loadedReadOnlyReason(s, profile, reads) {
|
|
319
346
|
let raw = typeof s === 'string' ? { session_id: s, profile: this.deps.activeProfile() }
|
|
320
347
|
: { session_id: s.session_id, profile: s.profile, source_tag: s.source_tag, raw_source: s.raw_source, session_source: s.session_source, read_only: s.read_only };
|
|
321
348
|
if (profile !== undefined)
|
|
322
349
|
raw = this.deps.profilesMatch(str(raw.profile) || null, profile) ? { ...raw, profile } : { session_id: raw.session_id, profile };
|
|
323
|
-
const row =
|
|
350
|
+
const row = (await this.ownerLocked([raw], reads ? (_ids, ownerProfile) => reads.owners(ownerProfile) : undefined))[0];
|
|
324
351
|
return row.read_only ? str(row.read_only_reason) || 'Read-only imported sessions cannot be modified from WebUI' : null;
|
|
325
352
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
353
|
+
/**
|
|
354
|
+
* The final owner check before a protected sidecar dispatch or write: callers dispatch as soon as it resolves. The
|
|
355
|
+
* owner is the Agent's, which can change at any moment, so reading it on the state.db worker (TAL-923) widens no
|
|
356
|
+
* window Web could close; the session lock, where one is held, still serializes Web's own writes.
|
|
357
|
+
*/
|
|
358
|
+
async assertWritable(session, profile, reads) {
|
|
359
|
+
const reason = await this.readOnlyReason(session, profile, reads);
|
|
360
|
+
if (reason)
|
|
361
|
+
throw new HttpFailure(403, reason);
|
|
332
362
|
}
|
|
333
363
|
/** Controls can exist without a local session file; use the same id the sidecar will address. */
|
|
334
364
|
async idForMutation(sid) {
|
|
@@ -350,16 +380,16 @@ export class SessionService {
|
|
|
350
380
|
}, opts);
|
|
351
381
|
}
|
|
352
382
|
/** Full session for mutation, refusing read-only imports (Python `_get_or_materialize_session`). */
|
|
353
|
-
async getForMutation(sid) {
|
|
354
|
-
let s = (await this.get404(sid));
|
|
383
|
+
async getForMutation(sid, reads) {
|
|
384
|
+
let s = (await this.get404(sid, {}, reads));
|
|
355
385
|
s = (await this.store.ensureFull(sid, s));
|
|
356
|
-
this.assertWritable(s);
|
|
386
|
+
await this.assertWritable(s, undefined, reads?.sid === s.session_id ? reads : undefined);
|
|
357
387
|
if (str(s.source_tag || s.raw_source).trim().toLowerCase() === 'subagent')
|
|
358
388
|
throw new HttpFailure(403, 'Read-only subagent child session');
|
|
359
389
|
return s;
|
|
360
390
|
}
|
|
361
|
-
async get404(sid, opts = {}) {
|
|
362
|
-
sid = this.resolveSessionId(sid);
|
|
391
|
+
async get404(sid, opts = {}, reads) {
|
|
392
|
+
sid = await this.resolveSessionId(sid);
|
|
363
393
|
try {
|
|
364
394
|
return (await this.store.get(sid, opts));
|
|
365
395
|
}
|
|
@@ -367,7 +397,7 @@ export class SessionService {
|
|
|
367
397
|
if (error instanceof SessionNotFound) {
|
|
368
398
|
// Python `_get_or_materialize_session`: a claimable foreign (CLI/TUI/Desktop) session gains a WebUI sidecar
|
|
369
399
|
// on its first mutation; a read-only foreign source answers 403 from `getForMutation`, a missing one 404.
|
|
370
|
-
const synth = (await this.claimOrSynthesizeCliSession(sid));
|
|
400
|
+
const synth = (await this.claimOrSynthesizeCliSession(sid, undefined, reads));
|
|
371
401
|
if (synth.session && synth.reason === 'materialized') {
|
|
372
402
|
await this.store.save(synth.session);
|
|
373
403
|
await this.store.touch(synth.session);
|
|
@@ -382,18 +412,33 @@ export class SessionService {
|
|
|
382
412
|
}
|
|
383
413
|
}
|
|
384
414
|
/** The Agent's state.db rows for this session (empty for subagent views, which never merge). */
|
|
385
|
-
stateDbRows(s) {
|
|
386
|
-
return this.stateDbRead(s).rows;
|
|
415
|
+
async stateDbRows(s) {
|
|
416
|
+
return (await this.stateDbRead(s)).rows;
|
|
387
417
|
}
|
|
388
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* `stateDbRows` and whether the read succeeded on a state.db whose messages carry ids (TAL-493). TAL-1162: read on the
|
|
420
|
+
* state.db worker; a worker failure is a failed read.
|
|
421
|
+
*/
|
|
389
422
|
stateDbRead(s) {
|
|
390
|
-
if (
|
|
391
|
-
return
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
423
|
+
if (isSubagentView(s))
|
|
424
|
+
return Promise.resolve(failedStateDbRead());
|
|
425
|
+
return stateDbSessionReadAsync(failedStateDbRead(), 'stateDbSessionRead', this.stateDbPath(s.profile), s.session_id, { stitch: false, lineage: stateDbReadLineage(s) });
|
|
426
|
+
}
|
|
427
|
+
/** `stateDbRead`'s projection of the rows past `afterId` alone (the detail's page tail), on the state.db worker. */
|
|
428
|
+
stateDbTailRead(s, afterId, unidentified) {
|
|
429
|
+
return stateDbSessionReadAsync(failedStateDbRead(), 'stateDbSessionTailRead', this.stateDbPath(s.profile), s.session_id, afterId, { lineage: stateDbReadLineage(s), unidentified });
|
|
430
|
+
}
|
|
431
|
+
/** The one read a turn's settlement builds its context from and settles with (`stateDbSettlementRead`). */
|
|
432
|
+
settlementRead(s) {
|
|
433
|
+
const unchanged = { ...failedStateDbRead(), lineage: s.state_db_lineage ?? [s.session_id], rotated: null };
|
|
434
|
+
if (isSubagentView(s))
|
|
435
|
+
return Promise.resolve(unchanged);
|
|
436
|
+
return stateDbSessionReadAsync(unchanged, 'stateDbSettlementRead', this.stateDbPath(s.profile), s.session_id, s.state_db_lineage);
|
|
395
437
|
}
|
|
396
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* Preserve existing transcript provenance before a turn adds Agent rows from its starting database read. The stamp
|
|
440
|
+
* lands before this returns; the read follows.
|
|
441
|
+
*/
|
|
397
442
|
beginStateDbTurn(s) {
|
|
398
443
|
stampStateDbGeneration([...s.messages, ...s.context_messages], markedStateDbGeneration(s));
|
|
399
444
|
return this.stateDbRead(s);
|
|
@@ -406,7 +451,7 @@ export class SessionService {
|
|
|
406
451
|
* rows (a WebUI conversation continued from the CLI shows the CLI turns). This is the coordinate space `GET
|
|
407
452
|
* /api/session` exposes, so branching and the next model history slice/extend the same list.
|
|
408
453
|
*/
|
|
409
|
-
mergedTranscript(s, local
|
|
454
|
+
mergedTranscript(s, local, stateRows) {
|
|
410
455
|
if (!stateRows.length)
|
|
411
456
|
return local;
|
|
412
457
|
return mergeSessionMessagesAppendOnly(local, stateRows, { truncationWatermark: s.truncation_watermark, compressedWatermark: s.truncation_watermark_compressed, stateDbSeenId: currentStateDbSeenId(s, stateRows), stateDbGeneration: markedStateDbGeneration(s) });
|
|
@@ -416,7 +461,7 @@ export class SessionService {
|
|
|
416
461
|
* so the merge never replays a covered row and appends every row committed after the read. Call it after the boundary
|
|
417
462
|
* fields are set: the marker holds only while they and the row it names stay as recorded.
|
|
418
463
|
*/
|
|
419
|
-
markStateDbSeen(s, read
|
|
464
|
+
markStateDbSeen(s, read, opts = {}) {
|
|
420
465
|
// Freeze numeric provenance before replacing the marker, including after a database replacement.
|
|
421
466
|
const priorGeneration = markedStateDbGeneration(s);
|
|
422
467
|
if (priorGeneration !== null)
|
|
@@ -445,7 +490,7 @@ export class SessionService {
|
|
|
445
490
|
* turn started from but the transcript lacks (a CLI continuation read into its context) are kept before the turn.
|
|
446
491
|
*/
|
|
447
492
|
settleStateDb(s, turn) {
|
|
448
|
-
const read = turn.read
|
|
493
|
+
const read = turn.read;
|
|
449
494
|
const agentAdded = withoutRows(turn.agentRows ?? [], turn.previousContext);
|
|
450
495
|
const contextAdded = withoutRows(s.context_messages, turn.previousContext);
|
|
451
496
|
if (typeof turn.startGeneration === 'string') {
|
|
@@ -511,10 +556,10 @@ export class SessionService {
|
|
|
511
556
|
// The read follows the new lineage from here, and everything it holds now is covered.
|
|
512
557
|
// ponytail: a CLI row committed to the continuation before this settlement is covered with them; telling them apart
|
|
513
558
|
// needs the Agent to mark the rows it writes at rotation.
|
|
514
|
-
const lineage =
|
|
559
|
+
const lineage = read.lineage;
|
|
515
560
|
if (JSON.stringify(lineage) !== JSON.stringify(s.state_db_lineage ?? [s.session_id])) {
|
|
516
561
|
s.state_db_lineage = lineage.length > 1 ? lineage : null;
|
|
517
|
-
this.markStateDbSeen(s);
|
|
562
|
+
this.markStateDbSeen(s, read.rotated ?? failedStateDbRead());
|
|
518
563
|
}
|
|
519
564
|
}
|
|
520
565
|
/**
|
|
@@ -548,35 +593,70 @@ export class SessionService {
|
|
|
548
593
|
*/
|
|
549
594
|
modelContext(s, stateRows) {
|
|
550
595
|
const local = s.context_messages.length ? s.context_messages : s.messages.filter((m) => !m._error && !m._partial);
|
|
551
|
-
return local.some((m) => isContextCompressionMarker(m)) ? local : this.mergedTranscript(s, local, stateRows
|
|
596
|
+
return local.some((m) => isContextCompressionMarker(m)) ? local : this.mergedTranscript(s, local, stateRows);
|
|
597
|
+
}
|
|
598
|
+
/** `modelContext` over a state.db read taken now. */
|
|
599
|
+
async readModelContext(s) {
|
|
600
|
+
return this.modelContext(s, await this.stateDbRows(s));
|
|
552
601
|
}
|
|
553
602
|
/**
|
|
554
603
|
* TAL-518: the model context a `/btw` clone inherits: the chat's history plus its running turn's prompt, which
|
|
555
604
|
* deferred save keeps out of `messages` and `context_messages` until settlement.
|
|
556
605
|
*/
|
|
557
|
-
sideQuestionContext(s) {
|
|
558
|
-
const context = structuredClone(this.
|
|
606
|
+
async sideQuestionContext(s) {
|
|
607
|
+
const context = structuredClone(await this.readModelContext(s));
|
|
559
608
|
const pending = this.pendingTurn(s);
|
|
560
609
|
return pending ? withPendingUserTurn(context, pending) : context;
|
|
561
610
|
}
|
|
562
611
|
/** Python `_is_messaging_session_id`: from the WebUI metadata or the Agent's row. */
|
|
563
|
-
async isMessagingSession(sid) {
|
|
612
|
+
async isMessagingSession(sid, reads = this.sessionGateReads(sid)) {
|
|
564
613
|
try {
|
|
565
614
|
if (isMessagingSessionRecord((await this.store.get(sid, { metadataOnly: true })).compact()))
|
|
566
615
|
return true;
|
|
567
616
|
}
|
|
568
617
|
catch { /* absent */ }
|
|
569
|
-
const meta =
|
|
618
|
+
const meta = await reads.meta();
|
|
570
619
|
return meta !== null && isMessagingSessionRecord(meta);
|
|
571
620
|
}
|
|
572
|
-
|
|
621
|
+
sessionGateReads(sid) {
|
|
622
|
+
const profile = this.deps.activeProfile();
|
|
623
|
+
const dbPath = this.stateDbPath(profile);
|
|
624
|
+
const owners = new Map();
|
|
625
|
+
let row;
|
|
626
|
+
let meta;
|
|
627
|
+
let transcript;
|
|
628
|
+
let context;
|
|
629
|
+
let existing;
|
|
630
|
+
return {
|
|
631
|
+
sid,
|
|
632
|
+
owners: (ownerProfile = profile) => {
|
|
633
|
+
let pending = owners.get(ownerProfile);
|
|
634
|
+
if (!pending) {
|
|
635
|
+
pending = this.stateDbSources([sid], ownerProfile);
|
|
636
|
+
owners.set(ownerProfile, pending);
|
|
637
|
+
}
|
|
638
|
+
return pending;
|
|
639
|
+
},
|
|
640
|
+
row: () => row ??= stateDbSessionReadAsync(null, 'stateDbSessionRow', dbPath, sid),
|
|
641
|
+
meta: () => meta ??= this.lookupCliMeta(sid),
|
|
642
|
+
messages: (stitch) => stitch
|
|
643
|
+
? transcript ??= isClaudeCodeSessionId(sid) ? Promise.resolve(this.deps.claudeCode?.messages(sid) ?? []) : stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: true })
|
|
644
|
+
: context ??= stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: false }),
|
|
645
|
+
existing: () => existing ??= agentSessionRowsExistingAsync(dbPath, [sid]),
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile, else its plain
|
|
650
|
+
* row (a segment its chain's row does not name). Both are read by id: the sidebar listing would scan every session to
|
|
651
|
+
* rank its window (TAL-923).
|
|
652
|
+
*/
|
|
573
653
|
async lookupCliMeta(sid) {
|
|
574
654
|
try {
|
|
575
655
|
if (isClaudeCodeSessionId(sid))
|
|
576
656
|
return (await this.claudeCodeRows()).find((r) => str(r.session_id) === sid) ?? null;
|
|
577
657
|
const profile = this.deps.activeProfile();
|
|
578
|
-
return (await this.deps.
|
|
579
|
-
?? (await this.deps.
|
|
658
|
+
return (await this.deps.cliSessions(profile, { sourceFilter: null, sessionIds: [sid], sidebarRows: true })).rows.find((r) => str(r.session_id) === sid)
|
|
659
|
+
?? (await this.deps.cliSessions(profile, { sourceFilter: null, sessionIds: [sid] })).rows[0] ?? null;
|
|
580
660
|
}
|
|
581
661
|
catch {
|
|
582
662
|
return null;
|
|
@@ -611,22 +691,23 @@ export class SessionService {
|
|
|
611
691
|
* a WebUI sidecar. `materialized` sessions are writeable (the caller persists the sidecar), `not_claimable` ones are
|
|
612
692
|
* read-only stubs, `was_webui`/`no_foreign_state`/`invalid_sid` answer 404.
|
|
613
693
|
*/
|
|
614
|
-
async claimOrSynthesizeCliSession(sid, metaIn) {
|
|
694
|
+
async claimOrSynthesizeCliSession(sid, metaIn, reads) {
|
|
615
695
|
if (!isSafeSessionId(sid))
|
|
616
696
|
return { session: null, reason: 'invalid_sid' };
|
|
617
697
|
const profile = this.deps.activeProfile();
|
|
618
|
-
|
|
619
|
-
sid
|
|
620
|
-
|
|
698
|
+
sid = await this.resolveSessionId(sid);
|
|
699
|
+
if (reads?.sid !== sid)
|
|
700
|
+
reads = this.sessionGateReads(sid);
|
|
701
|
+
const [row, owners] = await Promise.all([reads.row(), reads.owners()]);
|
|
621
702
|
const stateDbSource = str(row?.source).trim().toLowerCase();
|
|
622
703
|
const subagentChild = stateDbSource === 'subagent';
|
|
623
|
-
const owners = this.stateDbSources([sid]);
|
|
624
704
|
const canonical = typeof owners?.get(sid) === 'object';
|
|
625
705
|
if (((await this.indexMarksWasWebui(sid)) || ((await this.store.wasDeleted(sid)) && ['', 'webui', 'fork'].includes(stateDbSource))) && !subagentChild && !canonical)
|
|
626
706
|
return { session: null, reason: 'was_webui' };
|
|
627
|
-
const msgs =
|
|
628
|
-
const meta = { ...(metaIn ?? (await
|
|
629
|
-
|
|
707
|
+
const msgs = await reads.messages(true);
|
|
708
|
+
const meta = { ...(metaIn ?? (await reads.meta()) ?? {}) };
|
|
709
|
+
// An owner receipt proves a session before its first turn (a fresh diagnostic chat, TAL-1040); a bare row does not.
|
|
710
|
+
if (!msgs.length && !(meta._lineage_root_id && meta._lineage_tip_id) && !canonical)
|
|
630
711
|
return { session: null, reason: 'no_foreign_state' };
|
|
631
712
|
if (row) {
|
|
632
713
|
if (!meta.source_tag && stateDbSource)
|
|
@@ -648,7 +729,7 @@ export class SessionService {
|
|
|
648
729
|
const claimable = owners !== null && isClaimableCliSource(meta, owners.get(sid) || stateDbSource);
|
|
649
730
|
const workspace = str(meta.workspace || meta.cwd).trim() || (await this.deps.workspaces.lastWorkspace(profile));
|
|
650
731
|
const defaults = (await this.store.deps.defaults(profile));
|
|
651
|
-
const context = Array.isArray(meta._lineage_ids) ?
|
|
732
|
+
const context = Array.isArray(meta._lineage_ids) ? await reads.messages(false) : null;
|
|
652
733
|
const session = new Session({
|
|
653
734
|
session_id: sid, title: str(meta.title) || 'CLI Session', workspace, model: str(meta.model) || 'unknown', model_provider: str(meta.model_provider) || null,
|
|
654
735
|
messages: msgs, created_at: Number(meta.created_at) || 0, updated_at: Number(meta.updated_at) || 0, profile: str(meta.profile) || null,
|
|
@@ -663,9 +744,9 @@ export class SessionService {
|
|
|
663
744
|
}, defaults);
|
|
664
745
|
return { session, reason: claimable ? 'materialized' : 'not_claimable' };
|
|
665
746
|
}
|
|
666
|
-
async mutationTarget(sid, verb) {
|
|
747
|
+
async mutationTarget(sid, verb, reads) {
|
|
667
748
|
try {
|
|
668
|
-
return (await this.getForMutation(sid));
|
|
749
|
+
return (await this.getForMutation(sid, reads));
|
|
669
750
|
}
|
|
670
751
|
catch (error) {
|
|
671
752
|
if (error instanceof SessionNotFound)
|
|
@@ -683,19 +764,21 @@ export class SessionService {
|
|
|
683
764
|
if (isClaudeCodeSessionId(sid))
|
|
684
765
|
throw new HttpFailure(400, `Claude Code sessions are view-only and cannot be ${verb} from WebUI`);
|
|
685
766
|
}
|
|
686
|
-
async rejectViewOnly(sid, verb) {
|
|
767
|
+
async rejectViewOnly(sid, verb, reads) {
|
|
687
768
|
sid = sid.trim();
|
|
769
|
+
if (reads?.sid !== sid)
|
|
770
|
+
reads = this.sessionGateReads(sid);
|
|
688
771
|
this.rejectClaudeCode(sid, verb);
|
|
689
772
|
let profile = this.deps.activeProfile();
|
|
690
773
|
try {
|
|
691
774
|
profile = (await this.store.get(sid, { metadataOnly: true, cacheOnMiss: false, promote: false })).profile || 'default';
|
|
692
775
|
}
|
|
693
776
|
catch { /* foreign row */ }
|
|
694
|
-
const owners =
|
|
777
|
+
const owners = await reads.owners(profile);
|
|
695
778
|
const owner = owners?.get(sid);
|
|
696
779
|
if (!owners || typeof owner === 'object')
|
|
697
780
|
throw new HttpFailure(403, typeof owner === 'object' ? owner.reason ?? CANONICAL_READ_ONLY_REASON : OWNERSHIP_UNAVAILABLE_REASON);
|
|
698
|
-
if ((await this.isSubagentViewOnly(sid)))
|
|
781
|
+
if ((await this.isSubagentViewOnly(sid, reads)))
|
|
699
782
|
throw new HttpFailure(400, `Subagent sessions are view-only and cannot be ${verb} from WebUI`);
|
|
700
783
|
return sid;
|
|
701
784
|
}
|
|
@@ -717,9 +800,13 @@ export class SessionService {
|
|
|
717
800
|
}
|
|
718
801
|
return redactSessionData(payload, this.deps.redactEnabled());
|
|
719
802
|
}
|
|
803
|
+
/** Opaque read identity, including context changes that do not alter display message counts. */
|
|
804
|
+
contextDetailKey(s) {
|
|
805
|
+
return createHash('sha256').update(JSON.stringify([s.session_id, s.profile, s.model, s.model_provider, s.workspace, s.updated_at, s.messages.length, s.context_messages, s.post_compression_context_tokens_estimate, s.active_stream_id])).digest('hex');
|
|
806
|
+
}
|
|
720
807
|
/** `compact()` with the wire streaming/read-only flags (TAL-312), for replies that return the session row. */
|
|
721
808
|
async wireRow(s) {
|
|
722
|
-
return withSessionWireFlags(
|
|
809
|
+
return withSessionWireFlags((await this.ownerLocked([{ ...s.compact({ contextLengthFor: this.deps.contextLengthFor, modelOptionFor: this.deps.modelOptionFor }), context_detail_key: this.contextDetailKey(s), assistant_name: this.assistantName(s), workspace_name: (await this.workspaceNames([s]))(s) }]))[0], this.deps.runtime.activeStreamIds);
|
|
723
810
|
}
|
|
724
811
|
/**
|
|
725
812
|
* TAL-303: the label for a session's workspace, from its own profile's registry (a profileless row is the root's).
|
|
@@ -765,7 +852,7 @@ export class SessionService {
|
|
|
765
852
|
async deriveRange(s, rows, baseIndex, initialTurn, journaled, refs, calls, reviewRows) {
|
|
766
853
|
return (await this.backgroundLinked(s, hydrateAnchorActivityScenes(withToolCallOutcomes((await this.backgroundUpdates(s, withMarkerKinds(withTurnIds(withAttachmentObjects(rows), baseIndex, initialTurn), s), reviewRows)), calls, s.active_stream_id, baseIndex), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, runningScene: !journaled, clipToolResults: true, baseIndex, refCounts: refs })));
|
|
767
854
|
}
|
|
768
|
-
async limitedDetail(s, index, limit, before, pending, journaled) {
|
|
855
|
+
async limitedDetail(s, index, limit, before, pending, journaled, retried = false) {
|
|
769
856
|
let prefixEnd = s.messages.length;
|
|
770
857
|
const turnId = pending?.turnId ?? journaled?.turnId;
|
|
771
858
|
if (turnId)
|
|
@@ -797,8 +884,18 @@ export class SessionService {
|
|
|
797
884
|
let effectiveSeenId = null;
|
|
798
885
|
let cacheable = true;
|
|
799
886
|
let appended = null;
|
|
800
|
-
|
|
801
|
-
|
|
887
|
+
// TAL-1162: the transcript reads run on the state.db worker. A session that changed meanwhile no longer matches this
|
|
888
|
+
// index or the lifecycle snapshots: null asks the caller to take them again, and a second change is answered as
|
|
889
|
+
// read, uncached.
|
|
890
|
+
const [localRows, localCount, updatedAt, seenId] = [s.messages, s.messages.length, s.updated_at, s.state_db_seen_id];
|
|
891
|
+
const changed = () => s.messages !== localRows || s.messages.length !== localCount || s.updated_at !== updatedAt || s.state_db_seen_id !== seenId;
|
|
892
|
+
if (typeof seenId === 'number' && !isSubagentView(s)) {
|
|
893
|
+
const read = await this.stateDbTailRead(s, seenId, [...index.unidentified, ...context.unidentified]);
|
|
894
|
+
if (changed()) {
|
|
895
|
+
if (!retried)
|
|
896
|
+
return null;
|
|
897
|
+
cacheable = false;
|
|
898
|
+
}
|
|
802
899
|
if (!read.ok)
|
|
803
900
|
cacheable = false;
|
|
804
901
|
const markerRows = read.anchor ? [read.anchor, ...read.rows] : read.rows;
|
|
@@ -808,14 +905,19 @@ export class SessionService {
|
|
|
808
905
|
if (!ambiguous) {
|
|
809
906
|
const includeUids = read.rows.some((m) => stateDbIdentityKeys(m).some((id) => id.startsWith('uid:')));
|
|
810
907
|
const mode = includeUids ? 1 : 0;
|
|
811
|
-
appended = appendStateDbIdentityRows(read.rows,
|
|
908
|
+
appended = appendStateDbIdentityRows(read.rows, seenId, index.identities[mode], index.unidentified[mode], index.generation, includeUids);
|
|
812
909
|
stateRows = read.rows;
|
|
813
|
-
effectiveSeenId =
|
|
910
|
+
effectiveSeenId = seenId;
|
|
814
911
|
}
|
|
815
912
|
}
|
|
816
913
|
}
|
|
817
914
|
if (appended === null) {
|
|
818
|
-
stateRows = this.stateDbRows(s);
|
|
915
|
+
stateRows = await this.stateDbRows(s);
|
|
916
|
+
if (changed()) {
|
|
917
|
+
if (!retried)
|
|
918
|
+
return null;
|
|
919
|
+
cacheable = false;
|
|
920
|
+
}
|
|
819
921
|
effectiveSeenId = currentStateDbSeenId(s, stateRows);
|
|
820
922
|
appended = this.mergedTranscript(s, s.messages, stateRows).slice(s.messages.length);
|
|
821
923
|
}
|
|
@@ -827,12 +929,12 @@ export class SessionService {
|
|
|
827
929
|
const tailIndex = new SessionPrefixIndex(tail, index.revision, index.generation, { toolCalls: index.toolCalls(prefixEnd, tail.length), baseIndex: prefixEnd, initialTurn: prefixEnd ? index.turnId(prefixEnd - 1) : 'legacy:start', timestampFloor: index.summary(prefixEnd).maxTimestamp, todoFloor: index.summary(prefixEnd).maxTodoTimestamp, parent: { index, end: prefixEnd } });
|
|
828
930
|
const enoughContext = !streaming && (context.enough || sanitizeMessagesForApi(context.marker ? context.local : mergeSessionMessagesAppendOnly(context.local, stateRows, { truncationWatermark: s.truncation_watermark, compressedWatermark: s.truncation_watermark_compressed, stateDbSeenId: effectiveSeenId, stateDbGeneration: index.generation }).filter((m) => !s.active_stream_id || m._turn_id !== s.active_stream_id), { maxMessages: 4 }).length >= 4);
|
|
829
931
|
const owners = new Map();
|
|
830
|
-
const sources = (ids, profile) => {
|
|
932
|
+
const sources = async (ids, profile) => {
|
|
831
933
|
const ownerKey = JSON.stringify([ids, profile]);
|
|
832
934
|
const existing = owners.get(ownerKey);
|
|
833
935
|
if (existing)
|
|
834
936
|
return existing;
|
|
835
|
-
const result = this.stateDbSources(ids, profile);
|
|
937
|
+
const result = await this.stateDbSources(ids, profile);
|
|
836
938
|
if (result)
|
|
837
939
|
owners.set(ownerKey, result);
|
|
838
940
|
return result;
|
|
@@ -923,7 +1025,7 @@ export class SessionService {
|
|
|
923
1025
|
async detail(sid, query) {
|
|
924
1026
|
if (!sid)
|
|
925
1027
|
throw new HttpFailure(400, 'session_id is required');
|
|
926
|
-
sid = this.resolveSessionId(sid);
|
|
1028
|
+
sid = await this.resolveSessionId(sid);
|
|
927
1029
|
const loadMessages = (query.messages ?? '1') !== '0';
|
|
928
1030
|
const msgLimit = parseMsgLimit(query.msg_limit ?? null);
|
|
929
1031
|
const msgBeforeRaw = query.msg_before;
|
|
@@ -942,11 +1044,28 @@ export class SessionService {
|
|
|
942
1044
|
throw new HttpFailure(404, 'Session not found');
|
|
943
1045
|
}
|
|
944
1046
|
await this.clearStaleStreamState(s);
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1047
|
+
return this.detailPayload(s, loadMessages, msgLimit, msgBefore, revisionBefore);
|
|
1048
|
+
}
|
|
1049
|
+
/** Project an already settled session without reloading it or running endpoint recovery/profile gates. */
|
|
1050
|
+
async windowedDetail(s, limit) {
|
|
1051
|
+
return this.detailPayload(s, true, limit, null, await this.loadRevision(s));
|
|
1052
|
+
}
|
|
1053
|
+
async detailPayload(s, loadMessages, msgLimit, msgBefore, revisionBefore) {
|
|
1054
|
+
// TAL-1162: the full transcript read runs on the state.db worker before the lifecycle snapshots are taken, so they
|
|
1055
|
+
// describe the session the payload is built from.
|
|
1056
|
+
const stateRows = loadMessages && msgLimit === null ? await this.stateDbRows(s) : [];
|
|
1057
|
+
let journaled = loadMessages ? (await this.journaledActiveTurn(s)) : null;
|
|
1058
|
+
let pending = loadMessages ? this.pendingTurn(s) : null;
|
|
1059
|
+
let prefix = loadMessages && msgLimit !== null ? this.prefixIndex(s, revisionBefore) : null;
|
|
1060
|
+
let limited = prefix && msgLimit !== null ? (await this.limitedDetail(s, prefix, msgLimit, msgBefore, pending, journaled)) : null;
|
|
1061
|
+
if (prefix && msgLimit !== null && !limited) {
|
|
1062
|
+
// The session changed during the page's transcript read: every snapshot is taken again, once.
|
|
1063
|
+
revisionBefore = await this.loadRevision(s);
|
|
1064
|
+
journaled = await this.journaledActiveTurn(s);
|
|
1065
|
+
pending = this.pendingTurn(s);
|
|
1066
|
+
prefix = this.prefixIndex(s, revisionBefore);
|
|
1067
|
+
limited = await this.limitedDetail(s, prefix, msgLimit, msgBefore, pending, journaled, true);
|
|
1068
|
+
}
|
|
950
1069
|
let transcript = loadMessages && !limited ? this.mergedTranscript(s, s.messages, stateRows) : [];
|
|
951
1070
|
if (pending && !limited)
|
|
952
1071
|
transcript = withPendingUserTurn(transcript, pending);
|
|
@@ -982,6 +1101,8 @@ export class SessionService {
|
|
|
982
1101
|
let toolCalls = loadMessages ? s.tool_calls : [];
|
|
983
1102
|
if (windowed)
|
|
984
1103
|
toolCalls = prefix ? prefix.toolCalls(offset, truncated.length, true) : toolCallsForMessageWindow(toolCalls, offset, truncated.length);
|
|
1104
|
+
if (loadMessages && s.active_stream_id && msgBefore === null)
|
|
1105
|
+
toolCalls = [...toolCalls, ...(this.deps.runtime.liveToolProgress?.(s.active_stream_id) ?? [])];
|
|
985
1106
|
const mergedCount = summaryCount ?? all.length;
|
|
986
1107
|
let mergedLast = summaryLast ?? 0;
|
|
987
1108
|
if (summaryLast === null && all.length) {
|
|
@@ -1019,7 +1140,7 @@ export class SessionService {
|
|
|
1019
1140
|
raw._msg_limit_max = MAX_MSG_LIMIT;
|
|
1020
1141
|
const revisionAfter = (await this.loadRevision(s));
|
|
1021
1142
|
raw._load_revision = revisionBefore !== null && revisionBefore === revisionAfter ? hashRevision(revisionBefore) : `unstable-${randomUUID().replace(/-/g, '')}`;
|
|
1022
|
-
Object.assign(raw,
|
|
1143
|
+
Object.assign(raw, (await this.ownerLocked([raw], limited?.sources))[0]);
|
|
1023
1144
|
raw.assistant_name = this.assistantName(s);
|
|
1024
1145
|
raw.workspace_name = (await this.workspaceNames([s]))(s);
|
|
1025
1146
|
raw.branched_from = (await this.branchedFrom(s));
|
|
@@ -1058,10 +1179,10 @@ export class SessionService {
|
|
|
1058
1179
|
// A read-only foreign parent (a cron run) has no sidecar and opens from the active profile's state.db while it
|
|
1059
1180
|
// has transcript rows and is not a deleted chat (`claimOrSynthesizeCliSession`'s `was_webui` and `no_foreign_state`).
|
|
1060
1181
|
const dbPath = this.stateDbPath(null);
|
|
1061
|
-
const row = stateDbSessionRow
|
|
1182
|
+
const row = await stateDbSessionReadAsync(null, 'stateDbSessionRow', dbPath, parentId);
|
|
1062
1183
|
if (!row || (await this.store.wasDeleted(parentId)) || (await this.indexMarksWasWebui(parentId)))
|
|
1063
1184
|
return null;
|
|
1064
|
-
if (!
|
|
1185
|
+
if (!(await stateDbSessionReadAsync(false, 'stateDbSessionHasMessages', dbPath, parentId)))
|
|
1065
1186
|
return null;
|
|
1066
1187
|
title = row.title;
|
|
1067
1188
|
}
|
|
@@ -1093,7 +1214,7 @@ export class SessionService {
|
|
|
1093
1214
|
}
|
|
1094
1215
|
/** A session without a sidecar, synthesized from state.db for this profile; 404/409 like the detail. */
|
|
1095
1216
|
async foreignSession(sid) {
|
|
1096
|
-
sid = this.resolveSessionId(sid);
|
|
1217
|
+
sid = await this.resolveSessionId(sid);
|
|
1097
1218
|
const meta = (await this.lookupCliMeta(sid));
|
|
1098
1219
|
const profile = str(meta?.profile) || null;
|
|
1099
1220
|
const profileAgnostic = str(meta?.source_tag || meta?.raw_source).trim().toLowerCase() === 'claude_code';
|
|
@@ -1125,7 +1246,7 @@ export class SessionService {
|
|
|
1125
1246
|
assistant_name: this.assistantName(synth), workspace_name: (await this.workspaceNames([synth]))(synth), confirms_deep_truncate: true,
|
|
1126
1247
|
};
|
|
1127
1248
|
attachTodoState(sess, msgs);
|
|
1128
|
-
const merged = withSessionWireFlags(
|
|
1249
|
+
const merged = withSessionWireFlags((await this.ownerLocked([meta ? mergeCliSidebarMetadata(sess, meta) : sess]))[0], this.deps.runtime.activeStreamIds);
|
|
1129
1250
|
const publicRow = redactSessionData(merged, this.deps.redactEnabled());
|
|
1130
1251
|
return this.owner ? await this.owner.project(publicRow, msgLimit, msgBefore, loadMessages) : publicRow;
|
|
1131
1252
|
}
|
|
@@ -1170,7 +1291,7 @@ export class SessionService {
|
|
|
1170
1291
|
const attachments = [...target.pending_attachments];
|
|
1171
1292
|
// The run's starting read died with its worker: the state.db rows from before it started stand in for it. Its own
|
|
1172
1293
|
// rows open with its prompt and run to the next prompt; anything else another client wrote meanwhile stays.
|
|
1173
|
-
const read = this.
|
|
1294
|
+
const read = await this.settlementRead(target);
|
|
1174
1295
|
const runStart = target.pending_started_at || events[0]?.created_at || this.deps.now();
|
|
1175
1296
|
const before = read.rows.filter((m) => Number(m.timestamp) < runStart);
|
|
1176
1297
|
const runRows = read.rows.filter((m) => Number(m.timestamp) >= runStart);
|
|
@@ -1235,17 +1356,25 @@ export class SessionService {
|
|
|
1235
1356
|
const cliRead = wantState ? await this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
|
|
1236
1357
|
const claudeCodeRows = params.showCliSessions && params.showClaudeCodeSessions ? (await this.claudeCodeRows()) : [];
|
|
1237
1358
|
let cliRows = cliRead ? [...cliRead.rows, ...claudeCodeRows] : undefined;
|
|
1359
|
+
let agentStorage = cliRead?.storage;
|
|
1360
|
+
// TAL-1040: diagnostic chats Web started are Web chats, read even when external Agent sessions are hidden.
|
|
1361
|
+
const webDiagnosticIds = new Set(params.sourceFilter ? [] : await stateDbWebDiagnosticIdsAsync(join(this.deps.profileHome(activeProfile), 'state.db')));
|
|
1362
|
+
const unread = [...webDiagnosticIds].filter((id) => !cliRows?.some((row) => sessionLineageIds(row).has(id)));
|
|
1363
|
+
if (unread.length) {
|
|
1364
|
+
const extra = await this.deps.cliSessions(activeProfile, { sourceFilter: null, sessionIds: unread, keepEmpty: true });
|
|
1365
|
+
cliRows = [...(cliRows ?? []), ...extra.rows];
|
|
1366
|
+
agentStorage ??= extra.storage;
|
|
1367
|
+
}
|
|
1238
1368
|
const gatewayIdentity = loadGatewaySessionIdentityMap(join(this.deps.profileHome(activeProfile), 'sessions', 'sessions.json'));
|
|
1239
1369
|
const truncatedSources = cliRead?.truncated;
|
|
1240
|
-
let agentStorage = cliRead?.storage;
|
|
1241
1370
|
if (cliRows) {
|
|
1242
1371
|
const expanded = await this.persistedAliasMetadata(webuiRows, cliRows);
|
|
1243
1372
|
cliRows = expanded.rows;
|
|
1244
1373
|
if (expanded.storage && (expanded.storage.state === 'corrupt' || agentStorage?.state === 'ok'))
|
|
1245
1374
|
agentStorage = expanded.storage;
|
|
1246
1375
|
}
|
|
1247
|
-
const lookups = await this.listStateDbLookups(webuiRows, (cliRows ?? []).map((r) => str(r.session_id)));
|
|
1248
|
-
const payload = (await buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), ...(agentStorage ? { agentStorage } : {}), gatewayIdentity, ...lookups, webuiRows, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch }));
|
|
1376
|
+
const lookups = await withStateDbListingReads(() => this.listStateDbLookups(webuiRows, (cliRows ?? []).map((r) => str(r.session_id))));
|
|
1377
|
+
const payload = (await buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), ...(agentStorage ? { agentStorage } : {}), gatewayIdentity, ...lookups, webuiRows, webDiagnosticIds, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch }));
|
|
1249
1378
|
return sessionListResponse(payload, this.deps.runtime, this.deps.redactEnabled(), this.deps.now(), await this.workspaceNames([...payload.sessions, ...payload.sidebar_reference_sessions]));
|
|
1250
1379
|
}
|
|
1251
1380
|
async search(q, opts) {
|
|
@@ -1255,7 +1384,7 @@ export class SessionService {
|
|
|
1255
1384
|
sessions = sessions.filter((r) => this.deps.profilesMatch(str(r.profile) || null, activeProfile));
|
|
1256
1385
|
const metadata = await this.persistedAliasMetadata(sessions);
|
|
1257
1386
|
sessions = reconcileLocalSessionRows(sessions, metadata.rows, this.deps.profilesMatch);
|
|
1258
|
-
sessions =
|
|
1387
|
+
sessions = await this.ownerLocked(sessions);
|
|
1259
1388
|
const redact = this.deps.redactEnabled();
|
|
1260
1389
|
const workspaceName = await this.workspaceNames(sessions);
|
|
1261
1390
|
const redactRow = (item) => {
|
|
@@ -1347,6 +1476,29 @@ export class SessionService {
|
|
|
1347
1476
|
input_tokens: inp, output_tokens: out, total_tokens: inp + out, estimated_cost: full.estimated_cost,
|
|
1348
1477
|
};
|
|
1349
1478
|
}
|
|
1479
|
+
async contextDetail(sid, expectedKey) {
|
|
1480
|
+
if (await this.owner?.isOwned(sid))
|
|
1481
|
+
return this.owner.contextDetail(sid, expectedKey);
|
|
1482
|
+
const session = await this.get404(sid);
|
|
1483
|
+
const profile = this.deps.activeProfile();
|
|
1484
|
+
if (session.profile && session.profile !== profile || await this.loadedReadOnlyReason(session))
|
|
1485
|
+
return unavailableContextDetail();
|
|
1486
|
+
const sidecar = this.deps.sidecar?.();
|
|
1487
|
+
if (!sidecar)
|
|
1488
|
+
return unavailableContextDetail();
|
|
1489
|
+
// The read owns no cache. Compression and model/workspace replacement invalidate its response.
|
|
1490
|
+
const marks = (current = session) => this.contextDetailKey(current);
|
|
1491
|
+
const before = marks();
|
|
1492
|
+
if (before !== expectedKey)
|
|
1493
|
+
return unavailableContextDetail('changed');
|
|
1494
|
+
try {
|
|
1495
|
+
const detail = await sidecar.call('chat.context_detail', { session_id: sid, profile_home: this.deps.profileHome(profile), model: session.model, model_provider: session.model_provider, workspace: session.workspace });
|
|
1496
|
+
return this.deps.activeProfile() === profile && before === marks(await this.get404(sid)) ? detail : unavailableContextDetail('changed');
|
|
1497
|
+
}
|
|
1498
|
+
catch {
|
|
1499
|
+
return unavailableContextDetail();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1350
1502
|
async usage(sid) {
|
|
1351
1503
|
const s = (await this.get404(sid));
|
|
1352
1504
|
const inp = Math.trunc(s.input_tokens) || 0;
|
|
@@ -1407,6 +1559,36 @@ export class SessionService {
|
|
|
1407
1559
|
this.publish('session_new', s.profile, s.session_id);
|
|
1408
1560
|
return s;
|
|
1409
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* TAL-1040: an `ignore_config` / `safe` chat starts through the active profile's Agent owner, which freezes the mode before
|
|
1564
|
+
* any Agent import. That owner reads no profile default, so the launch route is explicit: the requested model, else this
|
|
1565
|
+
* profile's default as a normal chat starts, with that provider's configured endpoint.
|
|
1566
|
+
*/
|
|
1567
|
+
async createDiagnostic(body, mode) {
|
|
1568
|
+
const profile = (typeof body.profile === 'string' && body.profile) || this.deps.activeProfile();
|
|
1569
|
+
if (profile !== this.deps.activeProfile())
|
|
1570
|
+
throw new HttpFailure(400, 'A diagnostic chat starts in the active profile');
|
|
1571
|
+
if (!this.owner)
|
|
1572
|
+
throw new HttpFailure(409, 'The Agent cannot start a chat in this mode', { code: 'execution_mode_unavailable', reason: 'gateway_unavailable' });
|
|
1573
|
+
const defaults = await this.store.deps.defaults(profile);
|
|
1574
|
+
let workspace;
|
|
1575
|
+
try {
|
|
1576
|
+
workspace = await this.resolveNewSessionWorkspace(body, null, profile);
|
|
1577
|
+
}
|
|
1578
|
+
catch (error) {
|
|
1579
|
+
throw new HttpFailure(400, error.message);
|
|
1580
|
+
}
|
|
1581
|
+
const [requested, requestedProvider] = this.deps.modelStateFromRequest(body.model, body.model_provider, null);
|
|
1582
|
+
const model = requested ?? defaults.model;
|
|
1583
|
+
const provider = requested ? requestedProvider : requestedProvider ?? defaults.modelProvider ?? null;
|
|
1584
|
+
if (!model)
|
|
1585
|
+
throw new HttpFailure(400, 'Choose a model before starting a diagnostic chat');
|
|
1586
|
+
const toolsets = this.validateToolsetsShape(body.enabled_toolsets);
|
|
1587
|
+
const route = provider ? this.deps.profileLaunchRoute?.(profile, provider) ?? { provider, baseUrl: null } : null;
|
|
1588
|
+
const sid = await this.owner.create(profile, typeof body.request_id === 'string' ? body.request_id : null, { execution_mode: mode, cwd: workspace ?? defaults.workspace, model,
|
|
1589
|
+
...(route ? { provider: route.provider } : {}), ...(route?.baseUrl ? { base_url: route.baseUrl } : {}), ...(toolsets ? { toolsets } : {}) });
|
|
1590
|
+
return this.detail(sid, {});
|
|
1591
|
+
}
|
|
1410
1592
|
// ── simple metadata mutations ────────────────────────────────────────────
|
|
1411
1593
|
/** Python `_persist_generated_session_title`: title + generated/manual flags under the session lock, then insights sync. */
|
|
1412
1594
|
async persistGeneratedTitle(sid, nextTitle, eventReason) {
|
|
@@ -1431,7 +1613,8 @@ export class SessionService {
|
|
|
1431
1613
|
return current;
|
|
1432
1614
|
}
|
|
1433
1615
|
async rename(sid, rawTitle) {
|
|
1434
|
-
const
|
|
1616
|
+
const reads = this.sessionGateReads(sid);
|
|
1617
|
+
const s = (await this.mutationTarget(sid, 'renamed', reads));
|
|
1435
1618
|
await this.withMutationLock(sid, async () => {
|
|
1436
1619
|
applySessionTitleRename(s, rawTitle);
|
|
1437
1620
|
await this.store.save(s);
|
|
@@ -1449,8 +1632,9 @@ export class SessionService {
|
|
|
1449
1632
|
return run;
|
|
1450
1633
|
}
|
|
1451
1634
|
async pinUnserialized(sid, pinRequested) {
|
|
1452
|
-
|
|
1453
|
-
|
|
1635
|
+
const reads = this.sessionGateReads(sid);
|
|
1636
|
+
await this.rejectViewOnly(sid, 'modified', reads);
|
|
1637
|
+
let s = (await this.get404(sid, {}, reads));
|
|
1454
1638
|
s = (await this.store.ensureFull(sid, s));
|
|
1455
1639
|
if (pinRequested && !s.pinned) {
|
|
1456
1640
|
const rows = (await allSessions(this.store));
|
|
@@ -1470,16 +1654,143 @@ export class SessionService {
|
|
|
1470
1654
|
this.publish('session_pin', s.profile, s.session_id);
|
|
1471
1655
|
return { ok: true, session: (await this.wireRow(s)) };
|
|
1472
1656
|
}
|
|
1657
|
+
/**
|
|
1658
|
+
* TAL-1254: relock the worktrees of every unarchived worktree chat (or just `only`) for the running sidecar, so the
|
|
1659
|
+
* Agent's worktree sweep reads them as live, and flag each one git no longer has as retained. Runs before a
|
|
1660
|
+
* (re)started sidecar reports ready and when a chat is unarchived. The Agent's sweep runs outside Talaria, so a lock
|
|
1661
|
+
* the sidecar could not confirm is retried until git takes it; meanwhile that tree keeps its flag.
|
|
1662
|
+
*/
|
|
1663
|
+
async retainWorktrees(call, only) {
|
|
1664
|
+
const { rows, unread } = only ? { rows: [only.compact()], unread: 0 } : await this.worktreeSessionRows();
|
|
1665
|
+
const targets = rows.filter((row) => !row.archived && typeof row.worktree_path === 'string' && row.worktree_path && typeof row.session_id === 'string');
|
|
1666
|
+
if (!targets.length) {
|
|
1667
|
+
if (unread)
|
|
1668
|
+
this.retryWorktreeRetention();
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
let results;
|
|
1672
|
+
try {
|
|
1673
|
+
({ results } = await call('worktree.retain', { paths: [...new Set(targets.map((row) => String(row.worktree_path)))] }));
|
|
1674
|
+
}
|
|
1675
|
+
catch (error) {
|
|
1676
|
+
this.retryWorktreeRetention();
|
|
1677
|
+
throw error;
|
|
1678
|
+
}
|
|
1679
|
+
const unconfirmed = targets.filter((row) => !results[String(row.worktree_path)]).length + unread;
|
|
1680
|
+
if (!only && unconfirmed !== this.worktreesUnconfirmed)
|
|
1681
|
+
this.deps.log(`[webui] ${String(unconfirmed)} worktree lock(s) not yet held by the sidecar${unconfirmed ? '; retrying' : ''}`);
|
|
1682
|
+
if (!only)
|
|
1683
|
+
this.worktreesUnconfirmed = unconfirmed;
|
|
1684
|
+
if (unconfirmed)
|
|
1685
|
+
this.retryWorktreeRetention();
|
|
1686
|
+
for (const row of targets) {
|
|
1687
|
+
const state = results[String(row.worktree_path)];
|
|
1688
|
+
const sid = String(row.session_id);
|
|
1689
|
+
if (!state)
|
|
1690
|
+
continue;
|
|
1691
|
+
try {
|
|
1692
|
+
// `extra` follows the transcript in the file, past what a metadata-only read sees.
|
|
1693
|
+
if (row._unread)
|
|
1694
|
+
continue;
|
|
1695
|
+
const s = await this.store.ensureFull(sid, await this.store.get(sid, { metadataOnly: true }));
|
|
1696
|
+
if ((state === 'missing') === (s.extra[WORKTREE_RETAINED_KEY] === true))
|
|
1697
|
+
continue;
|
|
1698
|
+
await this.withMutationLock(sid, async () => {
|
|
1699
|
+
s.extra = Object.fromEntries(Object.entries(s.extra).filter(([key]) => key !== WORKTREE_RETAINED_KEY));
|
|
1700
|
+
if (state === 'missing')
|
|
1701
|
+
s.extra[WORKTREE_RETAINED_KEY] = true;
|
|
1702
|
+
await this.store.save(s, { touchUpdatedAt: false });
|
|
1703
|
+
}, { allowReadOnly: true });
|
|
1704
|
+
this.publish('session_worktree', s.profile, sid);
|
|
1705
|
+
}
|
|
1706
|
+
catch (error) {
|
|
1707
|
+
if (error instanceof SessionNotFound)
|
|
1708
|
+
continue;
|
|
1709
|
+
this.deps.log(`[webui] could not record the worktree state of ${sid}: ${error.message}`);
|
|
1710
|
+
this.retryWorktreeRetention();
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Every persisted worktree chat, read from its own file: the index only narrows the files to read, since a crash
|
|
1716
|
+
* between a save and its index update can leave a session out of the index or with a stale `archived`. A file that
|
|
1717
|
+
* cannot be read now, or a session directory that cannot be listed, falls back to the index rows (`_unread`, so their
|
|
1718
|
+
* flags are left alone) and counts in `unread`, which keeps retention retrying until every file has been read.
|
|
1719
|
+
*/
|
|
1720
|
+
async worktreeSessionRows() {
|
|
1721
|
+
let indexed;
|
|
1722
|
+
try {
|
|
1723
|
+
indexed = new Map((await this.store.readIndexEntries()).map((row) => [str(row.session_id), row]));
|
|
1724
|
+
}
|
|
1725
|
+
catch {
|
|
1726
|
+
indexed = new Map();
|
|
1727
|
+
}
|
|
1728
|
+
let persisted;
|
|
1729
|
+
try {
|
|
1730
|
+
persisted = await this.store.persistedIds({ strict: true });
|
|
1731
|
+
}
|
|
1732
|
+
catch {
|
|
1733
|
+
// The session directory cannot be listed now: retain what the index names and retry until it can.
|
|
1734
|
+
const rows = [...indexed.values()].filter((row) => row.worktree_path).map((row) => ({ ...row, archived: false, _unread: true }));
|
|
1735
|
+
return { rows, unread: Math.max(1, rows.length) };
|
|
1736
|
+
}
|
|
1737
|
+
const candidates = [...persisted].filter((sid) => !indexed.has(sid) || indexed.get(sid)?.worktree_path);
|
|
1738
|
+
let unread = 0;
|
|
1739
|
+
const rows = await Promise.all(candidates.map(async (sid) => {
|
|
1740
|
+
try {
|
|
1741
|
+
return (await this.store.get(sid, { metadataOnly: true })).compact();
|
|
1742
|
+
}
|
|
1743
|
+
catch (error) {
|
|
1744
|
+
if (error instanceof SessionNotFound)
|
|
1745
|
+
return null;
|
|
1746
|
+
unread += 1;
|
|
1747
|
+
const row = indexed.get(sid);
|
|
1748
|
+
return row?.worktree_path ? { ...row, archived: false, _unread: true } : null;
|
|
1749
|
+
}
|
|
1750
|
+
}));
|
|
1751
|
+
return { rows: rows.filter((row) => row !== null), unread };
|
|
1752
|
+
}
|
|
1753
|
+
/** One pending retry at a time; a sidecar that is not ready by then retains everything in its next ready gate instead. */
|
|
1754
|
+
retryWorktreeRetention() {
|
|
1755
|
+
if (this.worktreeRetainRetry)
|
|
1756
|
+
return;
|
|
1757
|
+
this.worktreeRetainRetry = setTimeout(() => {
|
|
1758
|
+
this.worktreeRetainRetry = null;
|
|
1759
|
+
const sidecar = this.deps.sidecar?.();
|
|
1760
|
+
if (sidecar?.status !== 'ready')
|
|
1761
|
+
return;
|
|
1762
|
+
this.retainWorktrees((method, params, opts) => sidecar.call(method, params, opts)).catch((error) => { this.deps.log(`[webui] worktree lock retry failed: ${String(error)}`); });
|
|
1763
|
+
}, WORKTREE_RETAIN_RETRY_MS);
|
|
1764
|
+
this.worktreeRetainRetry.unref();
|
|
1765
|
+
}
|
|
1473
1766
|
async archive(sid, archived) {
|
|
1474
|
-
|
|
1475
|
-
|
|
1767
|
+
const reads = this.sessionGateReads(sid);
|
|
1768
|
+
await this.rejectViewOnly(sid, 'archived', reads);
|
|
1769
|
+
let s = (await this.get404(sid, {}, reads));
|
|
1476
1770
|
s = (await this.store.ensureFull(sid, s));
|
|
1477
1771
|
await this.withMutationLock(sid, async () => {
|
|
1772
|
+
s = await this.store.ensureFull(sid, await this.store.get(sid));
|
|
1773
|
+
const previous = { archived: s.archived, pinned: s.pinned };
|
|
1774
|
+
const wasArchived = (await this.store.loadMetadataOnly(sid))?.archived ?? false;
|
|
1478
1775
|
s.archived = archived;
|
|
1479
1776
|
if (archived)
|
|
1480
1777
|
s.pinned = false;
|
|
1481
|
-
|
|
1778
|
+
try {
|
|
1779
|
+
await this.store.save(s, { touchUpdatedAt: false });
|
|
1780
|
+
const saved = await this.store.loadMetadataOnly(sid);
|
|
1781
|
+
if (saved?.archived !== archived || saved.pinned !== s.pinned)
|
|
1782
|
+
throw new HttpFailure(409, 'Archive was not saved; reload the chat and retry');
|
|
1783
|
+
}
|
|
1784
|
+
catch (error) {
|
|
1785
|
+
Object.assign(s, previous);
|
|
1786
|
+
throw error;
|
|
1787
|
+
}
|
|
1788
|
+
if (archived && !wasArchived)
|
|
1789
|
+
await this.finalizeSession(sid, s.profile, 'archive', stateDbReadLineage(s) ?? undefined);
|
|
1482
1790
|
}, { allowReadOnly: true });
|
|
1791
|
+
const sidecar = this.deps.sidecar?.();
|
|
1792
|
+
if (!archived && sidecar)
|
|
1793
|
+
await this.retainWorktrees((method, params, opts) => sidecar.call(method, params, opts), s).catch(() => undefined);
|
|
1483
1794
|
this.publish('session_archive', s.profile, s.session_id);
|
|
1484
1795
|
return { ok: true, session: (await this.wireRow(s)), ...worktreeRetainedPayload(s) };
|
|
1485
1796
|
}
|
|
@@ -1507,10 +1818,11 @@ export class SessionService {
|
|
|
1507
1818
|
}
|
|
1508
1819
|
modelConfirmationKey = randomUUID();
|
|
1509
1820
|
async confirmModelSwitch(s, model, provider, token, retry = false) {
|
|
1821
|
+
const stateRows = await this.stateDbRows(s);
|
|
1510
1822
|
const profile = s.profile ?? this.deps.activeProfile();
|
|
1511
1823
|
const home = this.deps.profileHome(profile);
|
|
1512
1824
|
const context = contextUsage(s).context_used_tokens;
|
|
1513
|
-
const messages = this.modelContext(s);
|
|
1825
|
+
const messages = this.modelContext(s, stateRows);
|
|
1514
1826
|
const before = JSON.stringify([s.model, s.model_provider, context, s.context_length, messages]);
|
|
1515
1827
|
let decision;
|
|
1516
1828
|
try {
|
|
@@ -1522,7 +1834,8 @@ export class SessionService {
|
|
|
1522
1834
|
catch (error) {
|
|
1523
1835
|
throw new HttpFailure(503, `Cannot check model selection warnings: ${error.message}`);
|
|
1524
1836
|
}
|
|
1525
|
-
|
|
1837
|
+
const stateRowsAfter = await this.stateDbRows(s);
|
|
1838
|
+
if (before !== JSON.stringify([s.model, s.model_provider, contextUsage(s).context_used_tokens, s.context_length, this.modelContext(s, stateRowsAfter)])) {
|
|
1526
1839
|
if (retry)
|
|
1527
1840
|
throw new HttpFailure(503, 'The session changed while checking model warnings. Try the selection again.');
|
|
1528
1841
|
return this.confirmModelSwitch(s, model, provider, null, true);
|
|
@@ -1556,7 +1869,7 @@ export class SessionService {
|
|
|
1556
1869
|
const contextLength = changed ? (this.deps.resolveContextLength ? await this.deps.resolveContextLength(nextModel, provider, s.profile) : this.deps.contextLengthFor(nextModel, provider)) : s.context_length;
|
|
1557
1870
|
if (changed)
|
|
1558
1871
|
await this.confirmModelSwitch(s, nextModel ?? '', provider, body.confirm_token);
|
|
1559
|
-
this.assertWritable(s);
|
|
1872
|
+
await this.assertWritable(s);
|
|
1560
1873
|
s.workspace = newWs;
|
|
1561
1874
|
if (hasModel) {
|
|
1562
1875
|
s.model = nextModel;
|
|
@@ -1564,9 +1877,10 @@ export class SessionService {
|
|
|
1564
1877
|
if (changed) {
|
|
1565
1878
|
s.context_length = contextLength;
|
|
1566
1879
|
forgetContextFigures(s);
|
|
1567
|
-
await this.deps.runtime.evictAgent(sid);
|
|
1568
1880
|
}
|
|
1569
1881
|
}
|
|
1882
|
+
if (changed || oldWs !== newWs)
|
|
1883
|
+
await this.deps.runtime.evictAgent(sid);
|
|
1570
1884
|
await this.store.save(s);
|
|
1571
1885
|
});
|
|
1572
1886
|
if (str(oldWs) !== str(newWs))
|
|
@@ -1619,7 +1933,7 @@ export class SessionService {
|
|
|
1619
1933
|
throw new HttpFailure(400, 'keep_count must be non-negative');
|
|
1620
1934
|
const refused = await this.withMutationLock(sid, async () => {
|
|
1621
1935
|
// One state.db read decides and marks the boundary, so a row committed meanwhile is neither counted nor hidden.
|
|
1622
|
-
const read = this.stateDbRead(s);
|
|
1936
|
+
const read = await this.stateDbRead(s);
|
|
1623
1937
|
if (hold) {
|
|
1624
1938
|
// A failed read cannot say whether a CLI continuation would go too: fail closed. The Agent keeps a state.db for every
|
|
1625
1939
|
// profile it has run in, so a missing one is never evidence of a session without state.db rows.
|
|
@@ -1745,7 +2059,7 @@ export class SessionService {
|
|
|
1745
2059
|
return false;
|
|
1746
2060
|
let tokens;
|
|
1747
2061
|
try {
|
|
1748
|
-
tokens = (await sidecar.call('models.estimate_tokens', { messages: this.
|
|
2062
|
+
tokens = (await sidecar.call('models.estimate_tokens', { messages: await this.readModelContext(s) })).tokens;
|
|
1749
2063
|
}
|
|
1750
2064
|
catch {
|
|
1751
2065
|
return false;
|
|
@@ -1786,7 +2100,7 @@ export class SessionService {
|
|
|
1786
2100
|
async compressionTarget(sid) {
|
|
1787
2101
|
await this.rejectViewOnly(sid, 'compressed');
|
|
1788
2102
|
const s = (await this.mutationTarget(sid, 'compressed'));
|
|
1789
|
-
const stateRows = this.stateDbRows(s);
|
|
2103
|
+
const stateRows = await this.stateDbRows(s);
|
|
1790
2104
|
const context = this.compressionContext(s, stateRows);
|
|
1791
2105
|
return { s, history: sanitizeMessagesForApi(context), context, stateRows };
|
|
1792
2106
|
}
|
|
@@ -1830,7 +2144,7 @@ export class SessionService {
|
|
|
1830
2144
|
}
|
|
1831
2145
|
/** Python `_handle_conversation_rounds` (TAL-258): the session's rounds and whether the handoff dock is due. */
|
|
1832
2146
|
async conversationRounds(sid, since) {
|
|
1833
|
-
const rounds = countConversationRounds(
|
|
2147
|
+
const rounds = await stateDbSessionReadAsync(0, 'countConversationRounds', (await this.handoffTarget(sid)).dbPath, sid, since);
|
|
1834
2148
|
return { ok: true, rounds, threshold: CONVERSATION_ROUND_THRESHOLD, should_show: rounds >= CONVERSATION_ROUND_THRESHOLD };
|
|
1835
2149
|
}
|
|
1836
2150
|
/**
|
|
@@ -1839,40 +2153,41 @@ export class SessionService {
|
|
|
1839
2153
|
* back to a deterministic local summary rather than an error.
|
|
1840
2154
|
*/
|
|
1841
2155
|
async handoffSummary(sid, since) {
|
|
1842
|
-
|
|
2156
|
+
const reads = this.sessionGateReads(sid.trim());
|
|
2157
|
+
sid = (await this.rejectViewOnly(sid, 'summarized', reads));
|
|
1843
2158
|
const { local, profile, dbPath } = (await this.handoffTarget(sid));
|
|
1844
|
-
const rounds = countConversationRounds
|
|
2159
|
+
const rounds = await stateDbSessionReadAsync(0, 'countConversationRounds', dbPath, sid, since);
|
|
1845
2160
|
if (rounds < CONVERSATION_ROUND_THRESHOLD)
|
|
1846
2161
|
throw new HttpFailure(400, 'Not enough conversation rounds to generate a summary.');
|
|
1847
2162
|
// A running turn settles the transcript it started from, which would drop a card appended meanwhile.
|
|
1848
2163
|
if (local?.active_stream_id)
|
|
1849
2164
|
throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
|
|
1850
|
-
const all = stateDbSessionMessages
|
|
2165
|
+
const all = await stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: true });
|
|
1851
2166
|
const messages = (since === null ? all : all.filter((m) => { const ts = stateDbTimestampSeconds(m.timestamp); return ts !== null && ts > since; })).slice(-50);
|
|
1852
2167
|
if (messages.length < 2)
|
|
1853
2168
|
throw new HttpFailure(400, 'Not enough messages to summarize.');
|
|
1854
2169
|
const sidecar = this.deps.sidecar?.() ?? null;
|
|
1855
2170
|
await ensureAgentRuntimeCurrent(sidecar);
|
|
1856
|
-
const meta = local ? null : (await
|
|
1857
|
-
const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages);
|
|
2171
|
+
const meta = local ? null : (await reads.meta());
|
|
2172
|
+
const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages, [...all, ...(local?.messages ?? []), ...(local?.context_messages ?? [])]);
|
|
1858
2173
|
const pick = (row, ...keys) => { for (const key of keys) {
|
|
1859
2174
|
const v = str(row?.[key]).trim();
|
|
1860
2175
|
if (v)
|
|
1861
2176
|
return v;
|
|
1862
2177
|
} return ''; };
|
|
1863
2178
|
// Python `get_cli_sessions` never listed WebUI's own state.db rows, so they name no channel.
|
|
1864
|
-
const cliRow = meta ?? (await
|
|
2179
|
+
const cliRow = meta ?? (await reads.meta());
|
|
1865
2180
|
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;
|
|
1866
2181
|
await this.persistHandoffMarker(sid, profile, handoffMarker(sid, generated.summary, channel, rounds, generated.fallback, this.deps.now()));
|
|
1867
2182
|
return { ok: true, summary: generated.summary, message_count: messages.length, rounds, fallback: generated.fallback, ...(generated.warning ? { warning: generated.warning } : {}) };
|
|
1868
2183
|
}
|
|
1869
2184
|
/** The main model's summary at 700 tokens, once more at 1400 when it was cut off, else the local fallback. */
|
|
1870
|
-
async generateHandoffSummary(sidecar, profile, local, meta, messages) {
|
|
2185
|
+
async generateHandoffSummary(sidecar, profile, local, meta, messages, source) {
|
|
1871
2186
|
const fallback = (warning) => ({ summary: fallbackHandoffSummary(messages), fallback: true, ...(warning ? { warning: `Summary generation used local fallback: ${warning}` } : {}) });
|
|
1872
2187
|
if (!sidecar)
|
|
1873
2188
|
return fallback('the Agent sidecar is not running');
|
|
1874
2189
|
const [model, provider] = local ? this.deps.modelStateFromRequest(local.model, local.model_provider, local.model_provider) : [str(meta?.model).trim() || null, null];
|
|
1875
|
-
const prompt = [{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }];
|
|
2190
|
+
const prompt = withDataUseRequirements([{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }], source);
|
|
1876
2191
|
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 });
|
|
1877
2192
|
try {
|
|
1878
2193
|
let result = await ask(700);
|
|
@@ -1898,7 +2213,7 @@ export class SessionService {
|
|
|
1898
2213
|
const card = messageHandoffPayload(marker);
|
|
1899
2214
|
const toStateDb = async () => {
|
|
1900
2215
|
await this.rejectViewOnly(sid, 'summarized');
|
|
1901
|
-
if (sameHandoff(handoffPayload(stateDbTailToolContent
|
|
2216
|
+
if (sameHandoff(handoffPayload(await stateDbSessionReadAsync(null, 'stateDbTailToolContent', this.stateDbPath(profile), sid)), card))
|
|
1902
2217
|
return true;
|
|
1903
2218
|
const sidecar = this.deps.sidecar?.();
|
|
1904
2219
|
if (!sidecar)
|
|
@@ -2003,11 +2318,11 @@ export class SessionService {
|
|
|
2003
2318
|
catch {
|
|
2004
2319
|
throw new HttpFailure(404, 'Session not found');
|
|
2005
2320
|
}
|
|
2321
|
+
// One state.db read serves the check, the kept display rows, and the boundary; every gate below decides after it.
|
|
2322
|
+
const read = await this.stateDbRead(live);
|
|
2006
2323
|
const turn = live.active_stream_id;
|
|
2007
2324
|
if (turn && !waited(turn))
|
|
2008
2325
|
throw new HttpFailure(409, 'Session stream state changed during compression; please retry.');
|
|
2009
|
-
// One state.db read serves the check, the kept display rows, and the boundary.
|
|
2010
|
-
const read = this.stateDbRead(live);
|
|
2011
2326
|
const stateRows = read.rows;
|
|
2012
2327
|
const tail = turnTail(this.modelContext(live, stateRows).filter((m) => !turn || m._turn_id !== turn), context);
|
|
2013
2328
|
if (!tail || !turnTail(live.messages, s.messages))
|
|
@@ -2090,8 +2405,8 @@ export class SessionService {
|
|
|
2090
2405
|
}
|
|
2091
2406
|
}
|
|
2092
2407
|
}
|
|
2093
|
-
/**
|
|
2094
|
-
async finalizeSession(sid, profile, reason) {
|
|
2408
|
+
/** Profile-scoped, best-effort lifecycle delivery; archive only notifies plugins. */
|
|
2409
|
+
async finalizeSession(sid, profile, reason, lineage) {
|
|
2095
2410
|
const sidecar = this.deps.sidecar?.();
|
|
2096
2411
|
if (!sidecar || this.deps.profileDeleting?.(profile))
|
|
2097
2412
|
return [];
|
|
@@ -2099,10 +2414,11 @@ export class SessionService {
|
|
|
2099
2414
|
try {
|
|
2100
2415
|
return (await sidecar.call('chat.finalize_session', {
|
|
2101
2416
|
session_id: sid, profile_home: this.deps.profileHome(profile ?? this.deps.activeProfile()), reason,
|
|
2417
|
+
...(lineage ? { lineage } : {}),
|
|
2102
2418
|
}, { timeoutMs: 12_000 })).messages;
|
|
2103
2419
|
}
|
|
2104
|
-
catch
|
|
2105
|
-
this.deps.log(`[webui] session
|
|
2420
|
+
catch {
|
|
2421
|
+
this.deps.log(`[webui] session lifecycle (${reason}) failed`);
|
|
2106
2422
|
return [];
|
|
2107
2423
|
}
|
|
2108
2424
|
finally {
|
|
@@ -2114,6 +2430,8 @@ export class SessionService {
|
|
|
2114
2430
|
const s = (await this.mutationTarget(sid, 'modified'));
|
|
2115
2431
|
await this.withMutationLock(sid, async () => {
|
|
2116
2432
|
const messages = await this.finalizeSession(sid, s.profile, 'clear');
|
|
2433
|
+
// The boundary covers every row this read saw, the session's finalization rows included.
|
|
2434
|
+
const read = await this.stateDbRead(s);
|
|
2117
2435
|
const hadMessages = s.messages.length > 0;
|
|
2118
2436
|
truncateSessionAtKeep(s, 0);
|
|
2119
2437
|
s.messages = messages.map((content) => ({ role: 'local_notice', content, _notice_kind: 'session_finalize', timestamp: this.deps.now() }));
|
|
@@ -2139,7 +2457,7 @@ export class SessionService {
|
|
|
2139
2457
|
s.pending_user_source = null;
|
|
2140
2458
|
s.clear_generation = hadMessages ? randomUUID().replace(/-/g, '') : null;
|
|
2141
2459
|
applySessionTitleRename(s, 'Untitled');
|
|
2142
|
-
this.markStateDbSeen(s,
|
|
2460
|
+
this.markStateDbSeen(s, read, { boundary: true });
|
|
2143
2461
|
await this.store.save(s);
|
|
2144
2462
|
if (hadMessages) {
|
|
2145
2463
|
try {
|
|
@@ -2155,6 +2473,7 @@ export class SessionService {
|
|
|
2155
2473
|
await this.rejectViewOnly(sid, 'modified');
|
|
2156
2474
|
return this.withMutationLock(sid, async () => {
|
|
2157
2475
|
const s = (await this.mutationTarget(sid, 'modified'));
|
|
2476
|
+
const read = await this.stateDbRead(s);
|
|
2158
2477
|
const history = s.messages;
|
|
2159
2478
|
const lastUser = findLastUserIndex(history);
|
|
2160
2479
|
if (lastUser === null)
|
|
@@ -2170,7 +2489,7 @@ export class SessionService {
|
|
|
2170
2489
|
return { error: 'The last message has nothing to resend.' };
|
|
2171
2490
|
const removed = history.length - lastUser;
|
|
2172
2491
|
shrinkTo(s, lastUser);
|
|
2173
|
-
this.markStateDbSeen(s,
|
|
2492
|
+
this.markStateDbSeen(s, read, { boundary: true });
|
|
2174
2493
|
await this.store.save(s);
|
|
2175
2494
|
return { ok: true, last_user_text: lastUserText, last_user_prompt: lastUserPrompt, last_user_attachments: lastUserAttachments, removed_count: removed };
|
|
2176
2495
|
});
|
|
@@ -2179,6 +2498,7 @@ export class SessionService {
|
|
|
2179
2498
|
await this.rejectViewOnly(sid, 'modified');
|
|
2180
2499
|
return this.withMutationLock(sid, async () => {
|
|
2181
2500
|
const s = (await this.mutationTarget(sid, 'modified'));
|
|
2501
|
+
const read = await this.stateDbRead(s);
|
|
2182
2502
|
const history = s.messages;
|
|
2183
2503
|
const lastUser = findLastUserIndex(history);
|
|
2184
2504
|
if (lastUser === null)
|
|
@@ -2186,7 +2506,7 @@ export class SessionService {
|
|
|
2186
2506
|
const removedText = extractText(history[lastUser]?.content);
|
|
2187
2507
|
const removed = history.length - lastUser;
|
|
2188
2508
|
shrinkTo(s, lastUser);
|
|
2189
|
-
this.markStateDbSeen(s,
|
|
2509
|
+
this.markStateDbSeen(s, read, { boundary: true });
|
|
2190
2510
|
await this.store.save(s);
|
|
2191
2511
|
const preview = removedText.length > 40 ? `${removedText.slice(0, 40)}...` : removedText;
|
|
2192
2512
|
return { ok: true, removed_count: removed, removed_preview: preview };
|
|
@@ -2239,14 +2559,14 @@ export class SessionService {
|
|
|
2239
2559
|
catch { /* ignore */ }
|
|
2240
2560
|
}
|
|
2241
2561
|
// Python: `keep_count` indexes the merged display transcript, never the raw sidecar array.
|
|
2242
|
-
const
|
|
2562
|
+
const stateRows = await this.stateDbRows(source);
|
|
2563
|
+
const sourceMessages = this.mergedTranscript(source, source.messages, stateRows);
|
|
2243
2564
|
const forked = keepCount !== null ? sourceMessages.slice(0, keepCount) : [...sourceMessages];
|
|
2244
2565
|
const title = customTitle ?? `${source.title || 'Untitled'} (fork)`;
|
|
2245
2566
|
const forkKeep = keepCount ?? sourceMessages.length;
|
|
2246
2567
|
// The branch's model context must carry the retained state.db rows too: the new id has no state.db rows of its
|
|
2247
2568
|
// own, and the runner prefers a non-empty context over the displayed messages.
|
|
2248
|
-
const
|
|
2249
|
-
const sourceContext = stateRows.length ? this.mergedTranscript(source, source.context_messages.length ? source.context_messages : source.messages) : source.context_messages;
|
|
2569
|
+
const sourceContext = stateRows.length ? this.mergedTranscript(source, source.context_messages.length ? source.context_messages : source.messages, stateRows) : source.context_messages;
|
|
2250
2570
|
const forkedContext = copyJson(truncateContextForDisplayKeep(sourceContext, sourceMessages, forkKeep));
|
|
2251
2571
|
const branch = new Session({
|
|
2252
2572
|
workspace: source.workspace, model: source.model, model_provider: source.model_provider, profile: source.profile, title, messages: withMirrorSources(forked, source), project_id: source.project_id,
|
|
@@ -2263,11 +2583,11 @@ export class SessionService {
|
|
|
2263
2583
|
}
|
|
2264
2584
|
/** Python `read_session_lineage_report` on the active profile's state.db. */
|
|
2265
2585
|
lineageReport(sid) {
|
|
2266
|
-
return stateDbLineageReport
|
|
2586
|
+
return stateDbSessionReadAsync(emptyLineageReport(sid.trim()), 'stateDbLineageReport', this.stateDbPath(null), sid);
|
|
2267
2587
|
}
|
|
2268
2588
|
/** TAL-259: recovery reads the active profile's state.db, and a recovered sidecar joins that profile. */
|
|
2269
2589
|
async recoveryDeps() {
|
|
2270
|
-
return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: await this.deps.recoveryStampingSince(), isReadOnly: (sid, profile) => Boolean(
|
|
2590
|
+
return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: await this.deps.recoveryStampingSince(), isReadOnly: async (sid, profile) => Boolean((await this.ownerLocked([{ session_id: sid, profile }]))[0]?.read_only), log: this.deps.log };
|
|
2271
2591
|
}
|
|
2272
2592
|
async recoveryAudit() {
|
|
2273
2593
|
return (await auditSessionRecovery(await this.recoveryDeps()));
|
|
@@ -2301,6 +2621,7 @@ export class SessionService {
|
|
|
2301
2621
|
project_id: source.project_id, profile: source.profile, session_source: 'fork', personality: source.personality, enabled_toolsets: copyJson(source.enabled_toolsets),
|
|
2302
2622
|
context_length: source.context_length, threshold_tokens: source.threshold_tokens, gateway_routing: copyJson(source.gateway_routing), gateway_routing_history: copyJson(source.gateway_routing_history),
|
|
2303
2623
|
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,
|
|
2624
|
+
...(source.extra[WORKTREE_RETAINED_KEY] === true ? { [WORKTREE_RETAINED_KEY]: true } : {}),
|
|
2304
2625
|
compression_recovery_source_session_id: sid, compression_recovery_action: action,
|
|
2305
2626
|
// An empty model-facing transcript: the focused follow-up must not replay the exhausted context.
|
|
2306
2627
|
messages: [], context_messages: [], composer_draft: { text: '', files: [] },
|
|
@@ -2360,13 +2681,14 @@ export class SessionService {
|
|
|
2360
2681
|
throw new HttpFailure(400, 'session_id is required');
|
|
2361
2682
|
if (!isSafeSessionId(sid))
|
|
2362
2683
|
throw new HttpFailure(400, 'Invalid session_id');
|
|
2363
|
-
|
|
2684
|
+
const reads = this.sessionGateReads(sid);
|
|
2685
|
+
await this.rejectViewOnly(sid, 'deleted', reads);
|
|
2364
2686
|
// Python: a read-only import (persisted sidecar flag, sidebar metadata, or a foreign state.db owner such as a
|
|
2365
2687
|
// Claude Code session) is never deleted — that would erase the owner's authoritative transcript. An orphan has none.
|
|
2366
|
-
const orphaned = (await this.isOrphanedImport(sid));
|
|
2367
|
-
if (!orphaned && (await this.isReadOnlyImport(sid)))
|
|
2688
|
+
const orphaned = (await this.isOrphanedImport(sid, reads));
|
|
2689
|
+
if (!orphaned && (await this.isReadOnlyImport(sid, reads)))
|
|
2368
2690
|
throw new HttpFailure(400, 'Read-only imported sessions cannot be deleted from WebUI');
|
|
2369
|
-
if ((await this.isSubagentViewOnly(sid)))
|
|
2691
|
+
if ((await this.isSubagentViewOnly(sid, reads)))
|
|
2370
2692
|
throw new HttpFailure(400, 'Subagent sessions are view-only and cannot be deleted from WebUI');
|
|
2371
2693
|
const retained = (await (async () => { try {
|
|
2372
2694
|
return worktreeRetainedPayload((await this.store.get(sid, { metadataOnly: true })));
|
|
@@ -2382,8 +2704,8 @@ export class SessionService {
|
|
|
2382
2704
|
eventProfile = null;
|
|
2383
2705
|
}
|
|
2384
2706
|
// Python `_is_messaging_session_id`: decided before the JSON is gone, from WebUI metadata or the Agent's row.
|
|
2385
|
-
const isMessaging = (await this.isMessagingSession(sid));
|
|
2386
|
-
const knownIds = !isMessaging && !orphaned ? stateDbCompressionLineage
|
|
2707
|
+
const isMessaging = (await this.isMessagingSession(sid, reads));
|
|
2708
|
+
const knownIds = !isMessaging && !orphaned ? await stateDbSessionReadAsync([sid], 'stateDbCompressionLineage', this.stateDbPath(eventProfile), sid) : [sid];
|
|
2387
2709
|
const assertIdle = async () => { for (const id of knownIds)
|
|
2388
2710
|
if (await this.activeRunBlocking(id))
|
|
2389
2711
|
throw new HttpFailure(409, 'Session has an active run; stop it before deleting'); };
|
|
@@ -2693,7 +3015,7 @@ export class SessionService {
|
|
|
2693
3015
|
session = (await this.store.load(sid)) ?? session;
|
|
2694
3016
|
if (!this.visibleToActiveProfile(session.profile))
|
|
2695
3017
|
throw new HttpFailure(404, 'Session not found');
|
|
2696
|
-
return { session, transcript: this.mergedTranscript(session), stored: true };
|
|
3018
|
+
return { session, transcript: this.mergedTranscript(session, session.messages, await this.stateDbRows(session)), stored: true };
|
|
2697
3019
|
}
|
|
2698
3020
|
catch (error) {
|
|
2699
3021
|
if (error instanceof HttpFailure)
|
|
@@ -2819,6 +3141,18 @@ function findLastUserIndex(history) {
|
|
|
2819
3141
|
export function sanitizePaths(error) {
|
|
2820
3142
|
return str(error?.message ?? error).replace(/(?:(?:\/[a-zA-Z0-9_.-]+)+|(?:[A-Z]:\\[^\s]+))/g, '<path>');
|
|
2821
3143
|
}
|
|
3144
|
+
/** A failed state.db read says nothing (TAL-493). */
|
|
3145
|
+
function failedStateDbRead() {
|
|
3146
|
+
return { rows: [], idCapable: false, ok: false, reason: null, generation: null };
|
|
3147
|
+
}
|
|
3148
|
+
/** TAL-529: after a compression rotation the Agent writes to the continuation, so a read follows the recorded lineage. */
|
|
3149
|
+
function stateDbReadLineage(s) {
|
|
3150
|
+
return s.state_db_lineage?.[0] === s.session_id ? s.state_db_lineage : null;
|
|
3151
|
+
}
|
|
3152
|
+
/** A delegated child's view, which never merges state.db rows. */
|
|
3153
|
+
function isSubagentView(s) {
|
|
3154
|
+
return str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() === 'subagent';
|
|
3155
|
+
}
|
|
2822
3156
|
/** TAL-536: a dead run's own state.db rows: from its prompt to the next prompt (tool results and applied steers stay in it). */
|
|
2823
3157
|
function runStateDbRows(rows, prompt) {
|
|
2824
3158
|
const at = rows.findIndex((m) => looksLikeCurrentUserTurn(m, prompt));
|