@maudecode/talaria-web 1.19.0 → 1.21.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 +309 -2
- package/dist/api/automation-router.js +28 -2
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +646 -24
- package/dist/api/chat-router.js +54 -167
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +54 -0
- package/dist/api/git-router.js +18 -5
- 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 +3 -3
- package/dist/api/session-visibility.js +2 -2
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +1868 -79
- package/dist/api/sessions-router.js +72 -43
- 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 +45 -21
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +773 -39
- package/dist/api/tools-router.js +167 -45
- 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 +16 -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.d.ts +2 -0
- package/dist/redact.js +55 -16
- package/dist/redact.js.map +1 -1
- package/dist/release.js +5 -3
- package/dist/release.js.map +1 -1
- package/dist/runtime.js +118 -26
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +25 -21
- package/dist/server.js.map +1 -1
- package/dist/sessions/admission.d.ts +21 -0
- package/dist/sessions/admission.js +176 -0
- package/dist/sessions/admission.js.map +1 -0
- package/dist/sessions/anchor.d.ts +4 -1
- package/dist/sessions/anchor.js +9 -5
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +41 -3
- package/dist/sessions/background-tasks.js +110 -10
- 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/events.d.ts +3 -0
- package/dist/sessions/events.js +4 -0
- package/dist/sessions/events.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 +92 -4
- package/dist/sessions/gateway-sessions.js +762 -93
- 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 +12 -5
- package/dist/sessions/merge.js +65 -18
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +15 -4
- package/dist/sessions/prefix-index.js +35 -13
- package/dist/sessions/prefix-index.js.map +1 -1
- package/dist/sessions/prompt-queues.d.ts +36 -0
- package/dist/sessions/prompt-queues.js +318 -0
- package/dist/sessions/prompt-queues.js.map +1 -0
- 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/relay.d.ts +12 -1
- package/dist/sessions/relay.js +75 -17
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +132 -37
- package/dist/sessions/service.js +688 -160
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +2 -0
- package/dist/sessions/session.js +5 -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 +71 -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 +6 -1
- package/dist/sessions/store.js +68 -27
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +10 -7
- package/dist/sessions/streams.js +49 -14
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +11 -2
- package/dist/sessions/tool-display.js +45 -4
- 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 +27 -6
- package/dist/sessions/turn.js +186 -89
- 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/kanban.d.ts +10 -1
- package/dist/tools/kanban.js +73 -1
- package/dist/tools/kanban.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.d.ts +3 -1
- package/dist/tools/mcp.js +92 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/memory.d.ts +28 -0
- package/dist/tools/memory.js +120 -14
- package/dist/tools/memory.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 +77 -9
- package/dist/tools/update-notifications.js +390 -46
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +29 -3
- package/dist/tools/updates.js +166 -34
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/managed-buffers.d.ts +58 -0
- package/dist/workspace/managed-buffers.js +76 -0
- package/dist/workspace/managed-buffers.js.map +1 -0
- 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 +18 -8
- 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 +30 -15
- 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 +246 -32
- package/sidecar/talaria_sidecar/agent_services.py +157 -34
- 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 +304 -30
- package/sidecar/talaria_sidecar/methods/kanban.py +50 -3
- package/sidecar/talaria_sidecar/methods/managed_buffers.py +110 -0
- package/sidecar/talaria_sidecar/methods/mcp.py +55 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +139 -11
- package/sidecar/talaria_sidecar/methods/outbound_drafts.py +165 -0
- 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 +240 -213
- package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
- package/static/dist/assets/AppShell-c5989995.js +8 -0
- package/static/dist/assets/Button-nuaikybd.js +1 -0
- package/static/dist/assets/ChatPage-bfg4khph.js +6 -0
- package/static/dist/assets/ConnectTargets-k5wo9yo8.js +1 -0
- package/static/dist/assets/Dialog-o8vo2aws.js +1 -0
- package/static/dist/assets/ExtensionRoute-l1yjrso4.js +1 -0
- package/static/dist/assets/Field-j7o8cfps.js +1 -0
- package/static/dist/assets/HubRoute-efnwe4xz.js +2 -0
- package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
- package/static/dist/assets/Markdown-clqf5m22.js +2 -0
- package/static/dist/assets/MarkdownRenderer-ccgqrza3.js +302 -0
- package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
- package/static/dist/assets/OAuthSignIn-h7bjaqp6.js +1 -0
- package/static/dist/assets/ResourceSwitch-oai2zqaj.js +1 -0
- package/static/dist/assets/RightPanel-ho161a45.js +1 -0
- package/static/dist/assets/Select-d1g5a8bm.js +1 -0
- package/static/dist/assets/SessionListPanel-i7kaokhi.js +1 -0
- package/static/dist/assets/SettingsLayout-lnecm87w.js +1 -0
- package/static/dist/assets/States-easntmry.js +1 -0
- package/static/dist/assets/TerminalPanel-lxhwd4jl.js +16 -0
- package/static/dist/assets/Toaster-l4sciy8t.js +1 -0
- package/static/dist/assets/_app-mqexqbg8.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-n797jp72.js +1 -0
- package/static/dist/assets/_app.index-ne43mdw2.js +1 -0
- package/static/dist/assets/_app.insights-fqonovb4.js +1 -0
- package/static/dist/assets/_app.kanban-kysliv19.js +1 -0
- package/static/dist/assets/_app.logs-febut82r.js +1 -0
- package/static/dist/assets/_app.memory-ha86oj1c.js +1 -0
- package/static/dist/assets/_app.profiles-cnu3gxxq.js +1 -0
- package/static/dist/assets/_app.session._sessionId-ifzjpcgn.js +1 -0
- package/static/dist/assets/_app.settings-nmakdiaf.js +1 -0
- package/static/dist/assets/_app.settings._section-djglnjmi.js +3 -0
- package/static/dist/assets/_app.skills-f3wad8yo.js +1 -0
- package/static/dist/assets/_app.tasks-l10py1c0.js +1 -0
- package/static/dist/assets/_app.todos-bml1m3r1.js +1 -0
- package/static/dist/assets/_app.workspaces-f3d066lx.js +1 -0
- package/static/dist/assets/abnfDiagram-VCTEODGH-i53jc4yp.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-e3bv5m6f.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-pakekxu2.js +36 -0
- package/static/dist/assets/archive-ihqi7z2x.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-bc6bo1n6.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-btvwk2qx.js} +1 -1
- package/static/dist/assets/channel-jn4f8443.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-koouqbut.js → chunk-4HAMMTFA-dlwab11c.js} +20 -20
- package/static/dist/assets/{chunk-75Z2AOVW-bl4bhdnt.js → chunk-75Z2AOVW-dp5gh7i8.js} +1 -1
- package/static/dist/assets/chunk-DU6HZSFF-bi06zl1c.js +128 -0
- package/static/dist/assets/{chunk-F27PBJKO-d8xxi0zi.js → chunk-F27PBJKO-kvv1rnlh.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-ji0rx9by.js} +6 -6
- package/static/dist/assets/{chunk-GVQU2GXP-djn6gbyu.js → chunk-GVQU2GXP-nmz0j82p.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-vw4p5p7o.js → chunk-IMKFNOWR-kmi1qx88.js} +5 -5
- package/static/dist/assets/{chunk-L3NEJ4N5-n1hj77q8.js → chunk-L3NEJ4N5-xhb7mebj.js} +1 -1
- package/static/dist/assets/chunk-OSK3NFVY-5a9sgscq.js +10 -0
- package/static/dist/assets/{chunk-P2QGCYS3-onf1hz6c.js → chunk-P2QGCYS3-dfezp4vu.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-m8sqn7cs.js → chunk-POPQ4Y6H-gd2vq3c0.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ek4smc8x.js → chunk-PWAF6VOD-wvw2nksf.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-g9gfvefo.js → chunk-SHT3W25Y-f58q97px.js} +13 -13
- package/static/dist/assets/{chunk-SVP7TREG-f9nfv2cu.js → chunk-SVP7TREG-abgwpyz9.js} +4 -4
- package/static/dist/assets/{chunk-TICWLB2K-bnk3m1mq.js → chunk-TICWLB2K-h4sbs8ds.js} +6 -6
- package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-dzrw66se.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-dzrw66se.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/completionAudio-ce86msbu.js +1 -0
- package/static/dist/assets/copied-bz3gkazh.js +1 -0
- package/static/dist/assets/copy-m9h56qa8.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-g081vffs.js +1 -0
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-odxvjesk.js +62 -0
- package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
- package/static/dist/assets/dagre-GXQ25YYZ-cpdwzjs4.js +4 -0
- package/static/dist/assets/dagre-o5jmjz46.js +1 -0
- package/static/dist/assets/{diagram-S7CK7UJ4-m6aeoe7c.js → diagram-S7CK7UJ4-clv5mxvu.js} +4 -4
- package/static/dist/assets/diagram-UQ7AKVKN-m2ia1mbf.js +41 -0
- package/static/dist/assets/{diagram-VSXAHHWV-hbawi7hn.js → diagram-VSXAHHWV-ea6zv636.js} +1 -1
- package/static/dist/assets/diagram-VX7I27RA-oqlef685.js +24 -0
- package/static/dist/assets/diagram-Z3DM3KII-kjubcbwu.js +24 -0
- package/static/dist/assets/dist-carq1azj.js +44 -0
- package/static/dist/assets/dist-crc46di9.js +7 -0
- package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
- package/static/dist/assets/ebnfDiagram-PWID7BFC-fg0zri3e.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-eb7pmk3x.js +99 -0
- package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-gvi1fxhn.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-abekab15.js +1 -0
- package/static/dist/assets/folder-plus-ksaons88.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ma6jxj5i.js → ganttDiagram-EL5Y4UJY-jmisowrc.js} +1 -1
- package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-m7kt3u6e.js +1 -0
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-hzltvsod.js → gitGraphDiagram-WWUBYQGX-nq7vfzh2.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-gcu017ay.js +1 -0
- package/static/dist/assets/host-ipp4e84c.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-d6vbxlra.js +13 -0
- package/static/dist/assets/index-ey6qo0zx.css +2 -0
- package/static/dist/assets/info-A6RAGUB7-idazwfqe.js +1 -0
- package/static/dist/assets/infoDiagram-27XIBGKW-bb9jh8gl.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-e7bnwq7h.js → ishikawaDiagram-5VMMS53U-nqc132rv.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-fpvnhy44.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-ndzqm6fu.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-javud235.js +1 -0
- package/static/dist/assets/{line-h0ojzsvs.js → line-g625e7cj.js} +1 -1
- package/static/dist/assets/linear-jlwrvvy6.js +1 -0
- package/static/dist/assets/link-gfdaho08.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-iv6yq4xf.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-cwj5cl6k.js +1 -0
- package/static/dist/assets/mermaid-parser.core-plzkuzov.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-ojrgs569.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-ff4emacu.js +1 -0
- package/static/dist/assets/packet-AYTQ26CC-gyaf9s8h.js +1 -0
- package/static/dist/assets/pegDiagram-XKGWAZYB-bf6xj8mq.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-f5ymzelo.js +1 -0
- package/static/dist/assets/pieDiagram-E7YTZNPT-d0qjl3zi.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-oqy1mras.js} +1 -1
- package/static/dist/assets/queryKeys-x8fjxhut.js +9 -0
- package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
- package/static/dist/assets/radar-RG4KPBEZ-b6jcjvda.js +1 -0
- package/static/dist/assets/railroad-74A4TZTK-e3a6lk5k.js +1 -0
- package/static/dist/assets/railroad-abnf-HS5TGJTU-ff5hocrx.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-exkhyw4n.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-jefk9b7c.js +1 -0
- package/static/dist/assets/railroadDiagram-O6MQD6OU-e4amks1i.js +1 -0
- package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
- package/static/dist/assets/refresh-cw-lfp4u1oh.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-c77k1e2y.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-oa1cc52s.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-j20gqt22.js} +11 -11
- package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
- package/static/dist/assets/share._token-kmfoflw6.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-g5jkzd9k.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-co2ghkdp.js +1 -0
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-faw1opgh.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-m30cktqe.js +1 -0
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hfil433g.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-liuep6fl.js} +8 -8
- package/static/dist/assets/toast-zmb35u3r.js +56 -0
- package/static/dist/assets/treeView-Q6P3EWNA-np5d2jv2.js +1 -0
- package/static/dist/assets/treemap-WGGIJYW6-cmpltxh1.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-f1udzsjf.js +1 -0
- package/static/dist/assets/{useParams-gjc70qru.js → useParams-ierevvts.js} +1 -1
- package/static/dist/assets/{useSearch-e6ae539u.js → useSearch-i7nr9ypl.js} +1 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-b2g42qrg.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-lb0eqbff.js +1 -0
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-ermwaqha.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-dll0jldk.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
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* Read-only projection of the Agent's `state.db` (Python `api/agent_sessions.py`):
|
|
3
3
|
* importable agent session rows with compression-chain collapse, CLI visibility
|
|
4
4
|
* rules, the O(1) change fingerprint used by the gateway watcher, and existence
|
|
5
|
-
* probes. Uses `node:sqlite
|
|
6
|
-
*
|
|
5
|
+
* probes. Uses cached read-only `node:sqlite` handles with a bounded busy timeout
|
|
6
|
+
* and a fresh snapshot per read; no path here ever writes to the database.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, openSync, readSync, closeSync, statSync } from 'node:fs';
|
|
9
9
|
import { DatabaseSync } from 'node:sqlite';
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
11
|
import { Worker } from 'node:worker_threads';
|
|
12
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
13
|
+
import { startupTimingScope } from '../startup-timing.js';
|
|
12
14
|
import { str } from '../util.js';
|
|
13
15
|
import { stateDbMessageFingerprint, stateDbMessageKey } from './state-db-identity.js';
|
|
14
16
|
import { MESSAGING_SOURCES } from './source-kind.js';
|
|
@@ -118,6 +120,8 @@ export function isCliSessionRowVisible(row) {
|
|
|
118
120
|
// ── compression-chain projection ─────────────────────────────────────────
|
|
119
121
|
/** The Agent's `_CHAIN_CAP`: the most hops a walk along a compression lineage takes. Its visited check stops a cycle sooner. */
|
|
120
122
|
const COMPRESSION_CHAIN_CAP = 1000;
|
|
123
|
+
/** The most ids the Agent's `_resume_lineage_ids` returns, a tighter defensive bound than `_CHAIN_CAP`. */
|
|
124
|
+
const RESUME_LINEAGE_CAP = 100;
|
|
121
125
|
function isContinuationSession(parent, child) {
|
|
122
126
|
if (!parent || !child)
|
|
123
127
|
return false;
|
|
@@ -138,6 +142,37 @@ function isContinuationSession(parent, child) {
|
|
|
138
142
|
const ended = Number.parseFloat(str(endedAt));
|
|
139
143
|
return Number.isFinite(started) && Number.isFinite(ended) && started + 1.0 >= ended;
|
|
140
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* `ids` plus every session a compression continuation (`isContinuationSession`) joins to one of them, up and down, at
|
|
147
|
+
* most `COMPRESSION_CHAIN_CAP` in all: the rows `projectAgentSessionRows` needs to collapse their chains as the sidebar
|
|
148
|
+
* listing does. Forks and other children are never followed, so they cannot crowd a continuation out of the cap.
|
|
149
|
+
*/
|
|
150
|
+
function compressionChainIds(db, cols, ids) {
|
|
151
|
+
if (!['parent_session_id', 'end_reason'].every((c) => cols.has(c)))
|
|
152
|
+
return ids;
|
|
153
|
+
const fields = ['id', 'source', 'session_source', 'model_config', 'started_at', 'parent_session_id', 'end_reason', 'ended_at'].map((c) => cols.has(c) ? c : `NULL AS ${c}`).join(', ');
|
|
154
|
+
const row = db.prepare(`SELECT ${fields} FROM sessions WHERE id = ?`);
|
|
155
|
+
const children = db.prepare(`SELECT ${fields} FROM sessions WHERE parent_session_id = ?`);
|
|
156
|
+
const seen = new Set(ids);
|
|
157
|
+
const pending = [...ids];
|
|
158
|
+
const visit = (next) => { if (next && !seen.has(next) && seen.size < COMPRESSION_CHAIN_CAP) {
|
|
159
|
+
seen.add(next);
|
|
160
|
+
pending.push(next);
|
|
161
|
+
} };
|
|
162
|
+
while (pending.length) {
|
|
163
|
+
const current = row.get(pending.pop());
|
|
164
|
+
if (!current)
|
|
165
|
+
continue;
|
|
166
|
+
const parentId = str(current.parent_session_id);
|
|
167
|
+
if (parentId && isContinuationSession(row.get(parentId), current))
|
|
168
|
+
visit(parentId);
|
|
169
|
+
if (['compression', 'cli_close'].includes(str(current.end_reason)))
|
|
170
|
+
for (const child of children.all(str(current.id)))
|
|
171
|
+
if (isContinuationSession(current, child))
|
|
172
|
+
visit(str(child.id));
|
|
173
|
+
}
|
|
174
|
+
return [...seen];
|
|
175
|
+
}
|
|
141
176
|
function continuationRootId(rowsById, sessionId) {
|
|
142
177
|
if (!sessionId)
|
|
143
178
|
return null;
|
|
@@ -159,7 +194,7 @@ function continuationRootId(rowsById, sessionId) {
|
|
|
159
194
|
return rootId;
|
|
160
195
|
}
|
|
161
196
|
/** Python `_project_agent_session_rows`: collapse compression chains into one sidebar row pointing at the freshest importable tip. */
|
|
162
|
-
export function projectAgentSessionRows(rows) {
|
|
197
|
+
export function projectAgentSessionRows(rows, keepEmpty = false) {
|
|
163
198
|
const rowsById = new Map(rows.map((r) => [str(r.id), r]));
|
|
164
199
|
const childrenByParent = new Map();
|
|
165
200
|
const continuationChildIds = new Set();
|
|
@@ -225,8 +260,12 @@ export function projectAgentSessionRows(rows) {
|
|
|
225
260
|
let tip = row;
|
|
226
261
|
if (['compression', 'cli_close'].includes(str(row.end_reason)))
|
|
227
262
|
[tip, segments] = compressionTip(row);
|
|
228
|
-
|
|
263
|
+
// `keepEmpty` (a direct lookup of receipt-proven Web diagnostic chats, TAL-1040) keeps a turnless plain row.
|
|
264
|
+
if (!tip || positiveInt(tip.actual_message_count) <= 0) {
|
|
265
|
+
if (keepEmpty && tip === row)
|
|
266
|
+
projected.push({ ...row });
|
|
229
267
|
continue;
|
|
268
|
+
}
|
|
230
269
|
if (tip === row) {
|
|
231
270
|
projected.push({ ...row });
|
|
232
271
|
continue;
|
|
@@ -255,17 +294,96 @@ export function projectAgentSessionRows(rows) {
|
|
|
255
294
|
projected.sort((a, b) => score(b.last_activity, b.started_at) - score(a.last_activity, a.started_at));
|
|
256
295
|
return projected;
|
|
257
296
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
297
|
+
/** Bound descriptors and page caches even when deleted profiles are never read again. */
|
|
298
|
+
const STATE_DB_HANDLE_CACHE_MAX = 16;
|
|
299
|
+
const readonlyStateDbs = new Map();
|
|
300
|
+
const schemaCaches = new WeakMap();
|
|
301
|
+
/** Cache ownership ends at shutdown; worker termination also disposes its native SQLite resources. */
|
|
302
|
+
function closeStateDbs(paths = readonlyStateDbs.keys()) {
|
|
303
|
+
for (const path of paths) {
|
|
304
|
+
const cached = readonlyStateDbs.get(path);
|
|
305
|
+
if (cached?.db.isOpen)
|
|
306
|
+
cached.db.close();
|
|
307
|
+
readonlyStateDbs.delete(path);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
process.once('exit', () => { closeStateDbs(); });
|
|
311
|
+
/** One handle per file identity. The caller ends the read snapshot in finally, leaving closure to the cache. */
|
|
312
|
+
function openStateDbReadonly(dbPath, busyTimeoutMs = STATE_DB_BUSY_TIMEOUT_MS) {
|
|
313
|
+
const { ino, dev, mtimeNs } = statSync(dbPath, { bigint: true });
|
|
314
|
+
let cached = readonlyStateDbs.get(dbPath);
|
|
315
|
+
// Raw writes need not advance SQLite's change counter; revalidate the main file after any such change.
|
|
316
|
+
if (cached && (cached.ino !== ino || cached.dev !== dev || cached.mtimeNs !== mtimeNs || !cached.db.isOpen)) {
|
|
317
|
+
closeStateDbs([dbPath]);
|
|
318
|
+
cached = undefined;
|
|
319
|
+
}
|
|
320
|
+
if (!cached) {
|
|
321
|
+
if (readonlyStateDbs.size >= STATE_DB_HANDLE_CACHE_MAX)
|
|
322
|
+
closeStateDbs([readonlyStateDbs.keys().next().value]);
|
|
323
|
+
const db = new DatabaseSync(dbPath, { readOnly: true, timeout: busyTimeoutMs });
|
|
324
|
+
cached = { db, ino, dev, mtimeNs, schemaVersion: null, columns: new Map(), indexes: new Map() };
|
|
325
|
+
schemaCaches.set(db, cached);
|
|
326
|
+
}
|
|
327
|
+
readonlyStateDbs.delete(dbPath);
|
|
328
|
+
readonlyStateDbs.set(dbPath, cached);
|
|
329
|
+
const { db } = cached;
|
|
330
|
+
try {
|
|
331
|
+
db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.trunc(busyTimeoutMs))}`);
|
|
332
|
+
// The schema cookie and all probes/queries must describe the same committed snapshot.
|
|
333
|
+
db.exec('BEGIN');
|
|
334
|
+
const version = db.prepare('PRAGMA schema_version').get().schema_version;
|
|
335
|
+
if (cached.schemaVersion !== version) {
|
|
336
|
+
cached.columns.clear();
|
|
337
|
+
cached.indexes.clear();
|
|
338
|
+
cached.schemaVersion = version;
|
|
339
|
+
}
|
|
340
|
+
return db;
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
closeStateDbs([dbPath]);
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
265
346
|
}
|
|
266
|
-
|
|
347
|
+
function finishStateDbRead(db) {
|
|
348
|
+
try {
|
|
349
|
+
db.exec('COMMIT');
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// A failed read can poison COMMIT too; discard the handle without hiding the reader's failure result.
|
|
353
|
+
db.close();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** A failed probe throws, so unreadable storage is never mistaken for an older schema (TAL-785). */
|
|
267
357
|
function tableColumns(db, table) {
|
|
268
|
-
|
|
358
|
+
const cache = schemaCaches.get(db).columns;
|
|
359
|
+
let columns = cache.get(table);
|
|
360
|
+
if (!columns) {
|
|
361
|
+
columns = new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((r) => r.name));
|
|
362
|
+
cache.set(table, columns);
|
|
363
|
+
}
|
|
364
|
+
return columns;
|
|
365
|
+
}
|
|
366
|
+
function tableIndexes(db, table) {
|
|
367
|
+
const cache = schemaCaches.get(db).indexes;
|
|
368
|
+
const indexes = cache.get(table);
|
|
369
|
+
if (indexes)
|
|
370
|
+
return indexes;
|
|
371
|
+
try {
|
|
372
|
+
const found = new Set(db.prepare(`PRAGMA index_list(${table})`).all().map((r) => r.name));
|
|
373
|
+
cache.set(table, found);
|
|
374
|
+
return found;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return new Set();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** Agent `_sql_freshest_of`: the `last_activity_at` heartbeat can lag a newer message, so it never ranks alone. */
|
|
381
|
+
function freshestActivity(lastActivityAt, lastMessage) {
|
|
382
|
+
return `MAX(COALESCE(${lastActivityAt}, 0), COALESCE(${lastMessage}, 0), COALESCE(s.started_at, 0))`;
|
|
383
|
+
}
|
|
384
|
+
/** Stale Agent statistics can favor a non-covering index and read every message row. */
|
|
385
|
+
function lastMessageTimestamp(indexed, alias = 'm') {
|
|
386
|
+
return `(SELECT MAX(${alias}.timestamp) FROM messages ${alias}${indexed ? ' INDEXED BY idx_messages_session' : ''} WHERE ${alias}.session_id = s.id)`;
|
|
269
387
|
}
|
|
270
388
|
const LOCAL_POLICY_PREFIX = 'gateway.local_policy.v1:';
|
|
271
389
|
/** Read-only equivalent of Agent validate_local_lineage. A receipt, never a reset edge alone, proves ownership. */
|
|
@@ -279,8 +397,8 @@ function localLineages(db, sid) {
|
|
|
279
397
|
const meta = db.prepare('SELECT value FROM state_meta WHERE key = ?');
|
|
280
398
|
const fetch = db.prepare('SELECT * FROM sessions WHERE id IN (SELECT value FROM json_each(?))');
|
|
281
399
|
const messageCols = tableColumns(db, 'messages');
|
|
282
|
-
const lastMessage = ['session_id', 'timestamp'].every((c) => messageCols.has(c)) ?
|
|
283
|
-
const activity =
|
|
400
|
+
const lastMessage = ['session_id', 'timestamp'].every((c) => messageCols.has(c)) ? lastMessageTimestamp(tableIndexes(db, 'messages').has('idx_messages_session')) : 'NULL';
|
|
401
|
+
const activity = freshestActivity(cols.has('last_activity_at') ? 's.last_activity_at' : 'NULL', lastMessage);
|
|
284
402
|
// Agent _CHAIN_STEP_SQL prefers a compression child, then a live child, then recency and id.
|
|
285
403
|
const children = db.prepare(`SELECT s.* FROM sessions s WHERE s.parent_session_id = ? ORDER BY CASE WHEN s.end_reason = 'compression' THEN 0 WHEN s.ended_at IS NULL THEN 1 ELSE 2 END, ${activity} DESC, s.started_at DESC, s.id DESC`);
|
|
286
404
|
const explicitChild = (child, includeReset) => {
|
|
@@ -373,7 +491,6 @@ export function stateDbLocalTarget(dbPath, sid, opts = {}) {
|
|
|
373
491
|
return sid;
|
|
374
492
|
}
|
|
375
493
|
try {
|
|
376
|
-
db.exec('BEGIN');
|
|
377
494
|
const target = localLineages(db, sid).get(sid)?.ids.at(-1) ?? sid;
|
|
378
495
|
opts.onRead?.(target);
|
|
379
496
|
return target;
|
|
@@ -382,9 +499,15 @@ export function stateDbLocalTarget(dbPath, sid, opts = {}) {
|
|
|
382
499
|
return sid;
|
|
383
500
|
}
|
|
384
501
|
finally {
|
|
385
|
-
db
|
|
502
|
+
finishStateDbRead(db);
|
|
386
503
|
}
|
|
387
504
|
}
|
|
505
|
+
/** `stateDbLocalTarget` plus whether state.db answered (a missing one does), so only a real answer seeds a routing cache. */
|
|
506
|
+
function stateDbLocalTargetRead(dbPath, sid) {
|
|
507
|
+
let read = false;
|
|
508
|
+
const target = stateDbLocalTarget(dbPath, sid, { onRead: () => { read = true; } });
|
|
509
|
+
return { target, read };
|
|
510
|
+
}
|
|
388
511
|
export const AGENT_STORAGE_OK = Object.freeze({ state: 'ok', reason: null });
|
|
389
512
|
/**
|
|
390
513
|
* The corrupt-storage reason when `error` is SQLite's `SQLITE_CORRUPT` or `SQLITE_NOTADB` (the Agent's
|
|
@@ -404,6 +527,9 @@ export function stateDbReadFailure(error) {
|
|
|
404
527
|
return { state: 'corrupt', reason: corrupt };
|
|
405
528
|
return { state: 'unavailable', reason: `Agent session storage (state.db) could not be read: ${error instanceof Error ? error.message : String(error)}.` };
|
|
406
529
|
}
|
|
530
|
+
function outdatedStateDbStorage() {
|
|
531
|
+
return { state: 'outdated', reason: "state.db has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this." };
|
|
532
|
+
}
|
|
407
533
|
/**
|
|
408
534
|
* TAL-785: whether `state.db` can serve the session list: `corrupt` when SQLite reports the file damaged, `unavailable`
|
|
409
535
|
* when it cannot be read for another reason (permissions, a lock, an I/O error), `outdated` when a readable database
|
|
@@ -417,10 +543,10 @@ export function stateDbStorage(dbPath) {
|
|
|
417
543
|
const db = openStateDbReadonly(dbPath);
|
|
418
544
|
try {
|
|
419
545
|
if (!tableColumns(db, 'sessions').has('source'))
|
|
420
|
-
return
|
|
546
|
+
return outdatedStateDbStorage();
|
|
421
547
|
}
|
|
422
548
|
finally {
|
|
423
|
-
db
|
|
549
|
+
finishStateDbRead(db);
|
|
424
550
|
}
|
|
425
551
|
}
|
|
426
552
|
catch (error) {
|
|
@@ -441,45 +567,42 @@ export function setStateDbReadDelayForTests(ms) { readDelayMs = ms; }
|
|
|
441
567
|
* `idx_messages_session` exactly as Python does when its heal fails.
|
|
442
568
|
*/
|
|
443
569
|
export function readImportableAgentSessionRows(dbPath, opts = {}) {
|
|
570
|
+
const result = readImportableAgentSessionRowBatches(dbPath, [opts]).rows[0];
|
|
571
|
+
if (result instanceof Error)
|
|
572
|
+
throw result;
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
/** All listing windows share one read snapshot, schema, validated local lineages and ownership lookup. */
|
|
576
|
+
export function readImportableAgentSessionRowBatches(dbPath, queries) {
|
|
444
577
|
if (!existsSync(dbPath))
|
|
445
|
-
return [];
|
|
446
|
-
const limit = opts.limit === undefined ? 200 : opts.limit;
|
|
447
|
-
const exclude = opts.excludeSources === undefined ? ['cron', 'webui'] : opts.excludeSources;
|
|
448
|
-
const include = opts.includeSources ?? null;
|
|
578
|
+
return { rows: queries.map(() => []), storage: AGENT_STORAGE_OK };
|
|
449
579
|
if (readDelayMs > 0)
|
|
450
580
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, readDelayMs);
|
|
451
581
|
const db = openStateDbReadonly(dbPath);
|
|
452
582
|
try {
|
|
453
|
-
db.exec('BEGIN'); // Receipt and physical rows must belong to the same committed generation.
|
|
454
|
-
const local = localLineages(db, opts.sessionIds?.length === 1 ? opts.sessionIds[0] : undefined);
|
|
455
583
|
const sessionCols = tableColumns(db, 'sessions');
|
|
456
584
|
const messageCols = tableColumns(db, 'messages');
|
|
457
585
|
if (!sessionCols.has('source')) {
|
|
458
586
|
if (!warnedNoSourceColumn.has(dbPath)) {
|
|
459
587
|
warnedNoSourceColumn.add(dbPath);
|
|
460
|
-
|
|
588
|
+
queries[0]?.log?.(`[webui] agent session listing skipped: state.db at ${dbPath} has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this.`);
|
|
461
589
|
}
|
|
462
|
-
return [];
|
|
590
|
+
return { rows: queries.map(() => []), storage: outdatedStateDbStorage() };
|
|
463
591
|
}
|
|
592
|
+
const only = queries.length === 1 ? queries[0]?.sessionIds : undefined;
|
|
593
|
+
const local = localLineages(db, only?.length === 1 ? only[0] : undefined);
|
|
464
594
|
const col = (name) => (sessionCols.has(name) ? `s.${name}` : `NULL AS ${name}`);
|
|
465
595
|
const messagesHasSessionId = messageCols.has('session_id');
|
|
466
596
|
const messagesHasTimestamp = messageCols.has('timestamp');
|
|
467
597
|
const countCol = messageCols.has('id') ? 'id' : 'session_id';
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
messagesIndexPresent = indexes.has('idx_messages_session');
|
|
474
|
-
userMessagesIndexPresent = indexes.has('idx_messages_session_user');
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
messagesIndexPresent = false;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
598
|
+
const messageIndexes = messagesHasSessionId && messagesHasTimestamp ? tableIndexes(db, 'messages') : new Set();
|
|
599
|
+
const sessionIndexes = tableIndexes(db, 'sessions');
|
|
600
|
+
const hasPolicies = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'state_meta'").get());
|
|
601
|
+
const messagesIndexPresent = messageIndexes.has('idx_messages_session');
|
|
602
|
+
const userMessagesIndexPresent = messageIndexes.has('idx_messages_session_user');
|
|
480
603
|
let actualCountExpr;
|
|
481
604
|
let userCountExpr;
|
|
482
|
-
let
|
|
605
|
+
let baseLastActivityExpr;
|
|
483
606
|
let joinClause = '';
|
|
484
607
|
let groupByClause = '';
|
|
485
608
|
if (messagesHasSessionId) {
|
|
@@ -492,12 +615,12 @@ export function readImportableAgentSessionRows(dbPath, opts = {}) {
|
|
|
492
615
|
}
|
|
493
616
|
else
|
|
494
617
|
userCountExpr = actualCountExpr;
|
|
495
|
-
|
|
618
|
+
baseLastActivityExpr = messagesHasTimestamp ? lastMessageTimestamp(messagesIndexPresent, 'lm') : 'NULL';
|
|
496
619
|
}
|
|
497
620
|
else {
|
|
498
621
|
actualCountExpr = `COUNT(m.${countCol})`;
|
|
499
622
|
userCountExpr = messageCols.has('role') ? "EXISTS(SELECT 1 FROM messages um WHERE um.session_id = s.id AND LOWER(um.role) = 'user')" : `COUNT(m.${countCol})`;
|
|
500
|
-
|
|
623
|
+
baseLastActivityExpr = messagesHasTimestamp ? 'MAX(m.timestamp)' : 'NULL';
|
|
501
624
|
joinClause = 'LEFT JOIN messages m ON m.session_id = s.id';
|
|
502
625
|
groupByClause = 'GROUP BY s.id';
|
|
503
626
|
}
|
|
@@ -505,100 +628,151 @@ export function readImportableAgentSessionRows(dbPath, opts = {}) {
|
|
|
505
628
|
else {
|
|
506
629
|
actualCountExpr = 's.message_count';
|
|
507
630
|
userCountExpr = 's.message_count';
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
let orderBy = 'ORDER BY s.started_at DESC';
|
|
511
|
-
let latestMessagesCte = null;
|
|
512
|
-
let candidateOrder = 'ORDER BY s.started_at DESC';
|
|
513
|
-
const where = ['s.source IS NOT NULL'];
|
|
514
|
-
const params = [];
|
|
515
|
-
if (opts.sessionIds !== undefined) {
|
|
516
|
-
where.push('s.id IN (SELECT value FROM json_each(?))');
|
|
517
|
-
params.push(JSON.stringify(opts.sessionIds.map((id) => local.get(id)?.ids.at(-1) ?? id)));
|
|
518
|
-
}
|
|
519
|
-
const superseded = [...local].filter(([id, lineage]) => id !== lineage.ids.at(-1)).map(([id]) => id);
|
|
520
|
-
if (superseded.length) {
|
|
521
|
-
where.push('s.id NOT IN (SELECT value FROM json_each(?))');
|
|
522
|
-
params.push(JSON.stringify(superseded));
|
|
631
|
+
baseLastActivityExpr = 'NULL';
|
|
523
632
|
}
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
633
|
+
const candidates = (opts) => {
|
|
634
|
+
const limit = opts.limit === undefined ? 200 : opts.limit;
|
|
635
|
+
const exclude = opts.excludeSources === undefined ? ['cron', 'webui'] : opts.excludeSources;
|
|
636
|
+
const include = opts.includeSources ?? null;
|
|
637
|
+
let lastActivityExpr = baseLastActivityExpr;
|
|
638
|
+
let orderBy = 'ORDER BY s.started_at DESC';
|
|
639
|
+
let latestMessagesCte = null;
|
|
640
|
+
let candidateOrder = 'ORDER BY s.started_at DESC';
|
|
641
|
+
const where = ['s.source IS NOT NULL'];
|
|
642
|
+
const params = [];
|
|
643
|
+
if (opts.sessionIds !== undefined) {
|
|
644
|
+
where.push('s.id IN (SELECT value FROM json_each(?))');
|
|
645
|
+
const ids = opts.sessionIds.map((id) => local.get(id)?.ids.at(-1) ?? id);
|
|
646
|
+
params.push(JSON.stringify(opts.compressionChains ? compressionChainIds(db, sessionCols, ids) : ids));
|
|
647
|
+
}
|
|
648
|
+
const superseded = [...local].filter(([id, lineage]) => id !== lineage.ids.at(-1)).map(([id]) => id);
|
|
649
|
+
if (superseded.length) {
|
|
650
|
+
where.push('s.id NOT IN (SELECT value FROM json_each(?))');
|
|
651
|
+
params.push(JSON.stringify(superseded));
|
|
652
|
+
}
|
|
653
|
+
const policySource = (id) => `(SELECT CASE WHEN json_valid(value) THEN json_extract(value, '$.policy.source') END FROM state_meta WHERE key = '${LOCAL_POLICY_PREFIX}' || ${id})`;
|
|
654
|
+
const sourceSql = hasPolicies ? `COALESCE(${policySource('s.id')}, ${sessionCols.has('chat_id') ? policySource('s.chat_id') : 'NULL'}, s.source)` : 's.source';
|
|
655
|
+
const included = (include ?? []).map(String).filter(Boolean);
|
|
656
|
+
if (included.length) {
|
|
657
|
+
where.push(`${sourceSql} IN (${included.map(() => '?').join(', ')})`);
|
|
658
|
+
params.push(...included);
|
|
659
|
+
}
|
|
660
|
+
const excluded = (exclude ?? []).map(String).filter(Boolean);
|
|
661
|
+
if (excluded.length) {
|
|
662
|
+
where.push(`${sourceSql} NOT IN (${excluded.map(() => '?').join(', ')})`);
|
|
663
|
+
params.push(...excluded);
|
|
664
|
+
}
|
|
665
|
+
const preaggregated = messagesHasSessionId && messagesHasTimestamp && included.length === 1 && included[0] === 'cron' && !messagesIndexPresent;
|
|
666
|
+
// TAL-1163: the Agent's activity index preselects the window; exact recency is computed for those candidates only.
|
|
667
|
+
const activityIndexed = limit !== null && messagesHasSessionId && messagesHasTimestamp && sessionCols.has('last_activity_at')
|
|
668
|
+
&& sessionIndexes.has('idx_sessions_effective_activity');
|
|
669
|
+
if (activityIndexed) {
|
|
670
|
+
lastActivityExpr = freshestActivity('s.last_activity_at', lastActivityExpr);
|
|
671
|
+
orderBy = 'ORDER BY last_activity DESC';
|
|
672
|
+
candidateOrder = 'ORDER BY COALESCE(s.last_activity_at, s.started_at) DESC, s.started_at DESC, s.id DESC';
|
|
673
|
+
}
|
|
674
|
+
else if (preaggregated) {
|
|
675
|
+
orderBy = 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
|
|
676
|
+
latestMessagesCte = 'latest_messages AS (SELECT mx.session_id AS session_id, MAX(mx.timestamp) AS last_message_at FROM messages mx GROUP BY mx.session_id)';
|
|
677
|
+
candidateOrder = 'ORDER BY COALESCE(lm.last_message_at, s.started_at) DESC, s.started_at DESC';
|
|
678
|
+
}
|
|
679
|
+
else if (messagesHasSessionId && messagesHasTimestamp) {
|
|
680
|
+
orderBy = messagesIndexPresent ? 'ORDER BY COALESCE(last_activity, s.started_at) DESC' : 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
|
|
681
|
+
candidateOrder = `ORDER BY COALESCE(${lastMessageTimestamp(messagesIndexPresent, 'mx')}, s.started_at) DESC, s.started_at DESC`;
|
|
682
|
+
}
|
|
683
|
+
const selectSql = `SELECT s.id, s.title, s.model, s.message_count, s.started_at, s.source, ${['model_config', 'session_source', 'user_id', 'chat_id', 'chat_type', 'thread_id', 'session_key', 'origin_chat_id', 'origin_user_id', 'platform', 'parent_session_id', 'ended_at', 'end_reason'].map(col).join(', ')}, ${actualCountExpr} AS actual_message_count, ${userCountExpr} AS actual_user_message_count, ${lastActivityExpr} AS last_activity`;
|
|
684
|
+
let raw;
|
|
685
|
+
if (limit !== null) {
|
|
686
|
+
const resultLimit = Math.max(0, Math.trunc(limit));
|
|
687
|
+
if (resultLimit === 0)
|
|
688
|
+
return [];
|
|
689
|
+
const candidateLimit = Math.max(resultLimit * 8, resultLimit);
|
|
690
|
+
const candidateCte = latestMessagesCte
|
|
691
|
+
? `WITH ${latestMessagesCte}, candidates AS (SELECT s.id FROM sessions s LEFT JOIN latest_messages lm ON lm.session_id = s.id WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`
|
|
692
|
+
: `WITH candidates AS (SELECT s.id FROM sessions s WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`;
|
|
693
|
+
raw = db.prepare(`${candidateCte} ${selectSql} FROM sessions s JOIN candidates c ON c.id = s.id ${joinClause} ${groupByClause} ${orderBy}`).all(...params, candidateLimit);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
raw = db.prepare(`${selectSql} FROM sessions s ${joinClause} WHERE ${where.join(' AND ')} ${groupByClause} ${orderBy}`).all(...params);
|
|
697
|
+
}
|
|
698
|
+
return raw;
|
|
699
|
+
};
|
|
700
|
+
const batches = queries.map((opts) => {
|
|
701
|
+
try {
|
|
702
|
+
return candidates(opts);
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
return error;
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
const ids = [...new Set(batches.flatMap((rows) => rows instanceof Error ? [] : rows.map((r) => str(r.id))))];
|
|
709
|
+
let owners;
|
|
710
|
+
try {
|
|
711
|
+
owners = sessionSources(db, sessionCols, ids);
|
|
558
712
|
}
|
|
559
|
-
|
|
560
|
-
|
|
713
|
+
catch (error) {
|
|
714
|
+
if (queries.length === 1)
|
|
715
|
+
throw error;
|
|
716
|
+
// Retry ownership per window only on damage, so a background failure cannot hide healthy interactive rows.
|
|
561
717
|
}
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
718
|
+
const rows = batches.map((batch, i) => {
|
|
719
|
+
if (batch instanceof Error)
|
|
720
|
+
return batch;
|
|
721
|
+
let batchOwners = owners;
|
|
722
|
+
if (batchOwners === undefined) {
|
|
723
|
+
try {
|
|
724
|
+
batchOwners = sessionSources(db, sessionCols, batch.map((row) => str(row.id)));
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
return error;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
let raw = batch;
|
|
731
|
+
const opts = queries[i];
|
|
732
|
+
const limit = opts.limit === undefined ? 200 : opts.limit;
|
|
733
|
+
raw = raw.map((r) => { const owner = batchOwners.get(str(r.id)); return { ...r, source: typeof owner === 'object' ? owner.source : owner || r.source }; });
|
|
734
|
+
const keepEmpty = opts.keepEmpty === true && opts.sessionIds !== undefined;
|
|
735
|
+
let projected = projectAgentSessionRows(raw.map((r) => {
|
|
736
|
+
const lineage = local.get(str(r.id));
|
|
737
|
+
if (!lineage)
|
|
738
|
+
return { ...r };
|
|
739
|
+
const root = lineage.rows[0];
|
|
740
|
+
const title = lineage.title || r.title;
|
|
741
|
+
return { ...r, title, started_at: root.started_at, last_activity: r.last_activity ?? r.started_at, parent_session_id: root.parent_session_id,
|
|
742
|
+
_lineage_root_id: lineage.owner, _lineage_tip_id: r.id, _lineage_ids: [...lineage.ids], _compression_segment_count: lineage.ids.length };
|
|
743
|
+
}), keepEmpty);
|
|
744
|
+
projected = projected.map((r) => {
|
|
745
|
+
const parent = local.get(str(r.parent_session_id));
|
|
746
|
+
return { ...r, ...normalizeAgentSessionSource(r.source), ...(parent ? { _parent_lineage_root_id: parent.owner, parent_title: parent.title, parent_source: parent.rows.at(-1)?.source ?? null } : {}) };
|
|
747
|
+
});
|
|
748
|
+
if (!keepEmpty)
|
|
749
|
+
projected = projected.filter(isCliSessionRowVisible);
|
|
750
|
+
if (limit === null)
|
|
751
|
+
return projected;
|
|
752
|
+
const selected = projected.slice(0, Math.max(0, Math.trunc(limit)));
|
|
753
|
+
const have = new Set(selected.map((r) => str(r.id)));
|
|
754
|
+
const byId = new Map(projected.filter((r) => r.id).map((r) => [str(r.id), r]));
|
|
755
|
+
const pending = [...selected];
|
|
756
|
+
while (pending.length) {
|
|
757
|
+
const row = pending.pop();
|
|
758
|
+
if (lower(row.raw_source || row.source) !== 'subagent')
|
|
759
|
+
continue;
|
|
760
|
+
const parentId = str(row.parent_session_id);
|
|
761
|
+
if (!parentId || have.has(parentId))
|
|
762
|
+
continue;
|
|
763
|
+
const parent = byId.get(parentId);
|
|
764
|
+
if (!parent || lower(parent.raw_source || parent.source) !== 'subagent')
|
|
765
|
+
continue;
|
|
766
|
+
selected.push(parent);
|
|
767
|
+
have.add(parentId);
|
|
768
|
+
pending.push(parent);
|
|
769
|
+
}
|
|
770
|
+
return selected;
|
|
576
771
|
});
|
|
577
|
-
|
|
578
|
-
if (limit === null)
|
|
579
|
-
return projected;
|
|
580
|
-
const selected = projected.slice(0, Math.max(0, Math.trunc(limit)));
|
|
581
|
-
const have = new Set(selected.map((r) => str(r.id)));
|
|
582
|
-
const byId = new Map(projected.filter((r) => r.id).map((r) => [str(r.id), r]));
|
|
583
|
-
const pending = [...selected];
|
|
584
|
-
while (pending.length) {
|
|
585
|
-
const row = pending.pop();
|
|
586
|
-
if (lower(row.raw_source || row.source) !== 'subagent')
|
|
587
|
-
continue;
|
|
588
|
-
const parentId = str(row.parent_session_id);
|
|
589
|
-
if (!parentId || have.has(parentId))
|
|
590
|
-
continue;
|
|
591
|
-
const parent = byId.get(parentId);
|
|
592
|
-
if (!parent || lower(parent.raw_source || parent.source) !== 'subagent')
|
|
593
|
-
continue;
|
|
594
|
-
selected.push(parent);
|
|
595
|
-
have.add(parentId);
|
|
596
|
-
pending.push(parent);
|
|
597
|
-
}
|
|
598
|
-
return selected;
|
|
772
|
+
return { rows, storage: AGENT_STORAGE_OK };
|
|
599
773
|
}
|
|
600
774
|
finally {
|
|
601
|
-
db
|
|
775
|
+
finishStateDbRead(db);
|
|
602
776
|
}
|
|
603
777
|
}
|
|
604
778
|
/** Python `agent_session_rows_existing`: batch existence probe; assumes present on any failure so nothing is pruned by mistake. */
|
|
@@ -629,12 +803,61 @@ export function agentSessionRowsExisting(dbPath, sessionIds) {
|
|
|
629
803
|
return wanted;
|
|
630
804
|
}
|
|
631
805
|
finally {
|
|
632
|
-
db
|
|
806
|
+
finishStateDbRead(db);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/** Scheduler admissions are the identity of gateway-fired cron runs, whose session ids stay local-*. */
|
|
810
|
+
function cronAdmissionJobIds(db, terminalOnly = false) {
|
|
811
|
+
const found = new Map();
|
|
812
|
+
const cols = tableColumns(db, 'session_admissions');
|
|
813
|
+
if (!['request_id', 'target_session_id', 'principal_id'].every((col) => cols.has(col)) || (terminalOnly && !cols.has('status')))
|
|
814
|
+
return found;
|
|
815
|
+
// The principal predicate selects the Agent's partial covering index, excluding interactive admissions.
|
|
816
|
+
const rows = db.prepare(`SELECT target_session_id, request_id FROM session_admissions WHERE principal_id = 'cron-owner' AND request_id LIKE 'cron:%'${terminalOnly ? " AND status = 'terminal'" : ''}`).all();
|
|
817
|
+
for (const row of rows) {
|
|
818
|
+
const match = /^cron:([^:]+):([^:]+)$/.exec(row.request_id);
|
|
819
|
+
if (match)
|
|
820
|
+
found.set(row.target_session_id, match[1]);
|
|
821
|
+
}
|
|
822
|
+
return found;
|
|
823
|
+
}
|
|
824
|
+
/** Read-only admission lookup, built once per session listing; older Agent stores have none. */
|
|
825
|
+
export function stateDbCronJobIds(dbPath) {
|
|
826
|
+
if (!existsSync(dbPath))
|
|
827
|
+
return new Map();
|
|
828
|
+
let db;
|
|
829
|
+
try {
|
|
830
|
+
db = openStateDbReadonly(dbPath);
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
return new Map();
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
return cronAdmissionJobIds(db);
|
|
837
|
+
}
|
|
838
|
+
catch {
|
|
839
|
+
return new Map();
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
finishStateDbRead(db);
|
|
633
843
|
}
|
|
634
844
|
}
|
|
845
|
+
/** Shared job identity rule for completion links and sidebar titles, including legacy job ids with underscores. */
|
|
846
|
+
export function cronSessionJobId(sid, jobIds, admissions) {
|
|
847
|
+
const admitted = admissions.get(sid);
|
|
848
|
+
if (admitted)
|
|
849
|
+
return admitted;
|
|
850
|
+
const legacy = /^cron_(.+)_\d{8}_\d{6}$/.exec(sid);
|
|
851
|
+
if (legacy)
|
|
852
|
+
return legacy[1];
|
|
853
|
+
// Preserve prefix matching for older non-timestamp session ids.
|
|
854
|
+
return [...jobIds].filter((id) => sid.startsWith(`cron_${id}_`)).reduce((best, id) => best === null || id.length > best.length ? id : best, null);
|
|
855
|
+
}
|
|
635
856
|
/**
|
|
636
|
-
* Python `_latest_cron_session_info_for_jobs`: the newest-started cron session for each completed job
|
|
637
|
-
*
|
|
857
|
+
* Python `_latest_cron_session_info_for_jobs`: the newest-started cron session for each completed job, excluding
|
|
858
|
+
* non-terminal gateway admissions so the next run cannot steal the completion link. A session id
|
|
859
|
+
* belongs to its scheduler admission's job or its legacy timestamp identity. Non-timestamp ids retain the longest
|
|
860
|
+
* matching job prefix, so `a` never claims a known `a_b`'s sessions. A
|
|
638
861
|
* missing, locked, or unrecognized state.db answers an empty map; callers send no enrichment.
|
|
639
862
|
*/
|
|
640
863
|
export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
|
|
@@ -654,17 +877,23 @@ export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
|
|
|
654
877
|
const cols = tableColumns(db, 'sessions');
|
|
655
878
|
if (!cols.has('id') || !cols.has('source'))
|
|
656
879
|
return found;
|
|
880
|
+
const admissions = cronAdmissionJobIds(db, true);
|
|
657
881
|
const count = cols.has('message_count') ? 's.message_count' : 'NULL';
|
|
658
882
|
const order = cols.has('started_at') ? 'COALESCE(s.started_at, 0) DESC, s.id DESC' : 's.id DESC';
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
883
|
+
const indexed = tableIndexes(db, 'sessions').has('idx_sessions_source_id') ? ' INDEXED BY idx_sessions_source_id' : '';
|
|
884
|
+
const query = db.prepare(`SELECT s.id AS id, ${count} AS message_count FROM sessions s${indexed} WHERE s.source = 'cron' ORDER BY ${order} LIMIT ? OFFSET ?`);
|
|
885
|
+
const batchSize = wanted.size * 8;
|
|
886
|
+
// Stream older rows only if needed: a busy job must not hide another job's completion link.
|
|
887
|
+
for (const [limit, offset] of [[batchSize, 0], [-1, batchSize]]) {
|
|
888
|
+
for (const row of query.iterate(limit, offset)) {
|
|
889
|
+
const sid = typeof row.id === 'string' ? row.id : '';
|
|
890
|
+
const owner = cronSessionJobId(sid, ids, admissions);
|
|
891
|
+
if (owner === null || !wanted.has(owner) || found.has(owner))
|
|
892
|
+
continue;
|
|
893
|
+
found.set(owner, { session_id: sid, message_count: Number.isFinite(Number(row.message_count)) && row.message_count !== null ? Math.trunc(Number(row.message_count)) : null });
|
|
894
|
+
if (found.size === wanted.size)
|
|
895
|
+
return found;
|
|
896
|
+
}
|
|
668
897
|
}
|
|
669
898
|
return found;
|
|
670
899
|
}
|
|
@@ -672,7 +901,7 @@ export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
|
|
|
672
901
|
return new Map();
|
|
673
902
|
}
|
|
674
903
|
finally {
|
|
675
|
-
db
|
|
904
|
+
finishStateDbRead(db);
|
|
676
905
|
}
|
|
677
906
|
}
|
|
678
907
|
/** A source label remains display metadata; canonical receipts and ledgers independently retain write ownership. */
|
|
@@ -722,36 +951,49 @@ function sessionSources(db, cols, ids) {
|
|
|
722
951
|
receiptOwners.set(id, owner);
|
|
723
952
|
};
|
|
724
953
|
if (tables.has('state_meta')) {
|
|
725
|
-
const
|
|
954
|
+
const prefixes = [LOCAL_POLICY_PREFIX, 'gateway.retired_session.v1.', 'gateway.api.binding.v1.'];
|
|
955
|
+
const keys = candidates.flatMap((owner) => prefixes.map((prefix) => `${prefix}${owner}`));
|
|
956
|
+
const metadata = new Map();
|
|
957
|
+
const read = db.prepare('SELECT key, value FROM state_meta WHERE key IN (SELECT value FROM json_each(?))');
|
|
958
|
+
for (let i = 0; i < keys.length; i += 400) {
|
|
959
|
+
for (const row of read.all(JSON.stringify(keys.slice(i, i + 400))))
|
|
960
|
+
metadata.set(row.key, row.value);
|
|
961
|
+
}
|
|
962
|
+
// Keep receipt precedence in candidate order, independent of SQLite's key ordering.
|
|
726
963
|
for (const owner of candidates) {
|
|
727
|
-
const
|
|
728
|
-
if (
|
|
729
|
-
rememberPolicy(owner, policyValue(
|
|
730
|
-
if (
|
|
964
|
+
const key = `${LOCAL_POLICY_PREFIX}${owner}`;
|
|
965
|
+
if (metadata.has(key))
|
|
966
|
+
rememberPolicy(owner, policyValue(metadata.get(key)));
|
|
967
|
+
if (metadata.has(`gateway.retired_session.v1.${owner}`))
|
|
731
968
|
retired.add(owner);
|
|
732
|
-
if (
|
|
969
|
+
if (metadata.has(`gateway.api.binding.v1.${owner}`))
|
|
733
970
|
bindings.add(owner);
|
|
734
971
|
}
|
|
735
972
|
}
|
|
736
973
|
const ledgerIds = new Set();
|
|
974
|
+
const ledgerCandidates = JSON.stringify(candidates);
|
|
737
975
|
for (const [table, column] of [['session_admissions', 'target_session_id'], ['worker_executions', 'session_id']]) {
|
|
738
|
-
if (!tables.has(table))
|
|
976
|
+
if (!tables.has(table) || !candidates.length)
|
|
739
977
|
continue;
|
|
740
978
|
const hasLineage = tableColumns(db, table).has('lineage_json');
|
|
741
979
|
for (let i = 0; i < candidates.length; i += 400) {
|
|
742
980
|
const chunk = candidates.slice(i, i + 400);
|
|
743
981
|
const marks = chunk.map(() => '?').join(',');
|
|
744
|
-
const
|
|
745
|
-
const found = db.prepare(`SELECT ${column} AS id, ${hasLineage ? 'lineage_json' : 'NULL AS lineage_json'} FROM ${table} WHERE ${column} IN (${marks})${linked}`).all(...chunk, ...(hasLineage ? chunk : []));
|
|
746
|
-
for (const row of found) {
|
|
982
|
+
for (const row of db.prepare(`SELECT ${column} AS id FROM ${table} WHERE ${column} IN (${marks})`).all(...chunk))
|
|
747
983
|
ledgerIds.add(row.id);
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
984
|
+
}
|
|
985
|
+
if (hasLineage) {
|
|
986
|
+
// Bind the complete set once so unrelated history never crosses into JavaScript, even across candidate chunks.
|
|
987
|
+
const scan = db.prepare(`SELECT ${column} AS id, lineage_json FROM ${table} WHERE lineage_json IS NOT NULL AND
|
|
988
|
+
(${column} IN (SELECT value FROM json_each(?)) OR EXISTS
|
|
989
|
+
(SELECT 1 FROM json_each(lineage_json) WHERE value IN (SELECT value FROM json_each(?))))`);
|
|
990
|
+
for (const row of scan.iterate(ledgerCandidates, ledgerCandidates)) {
|
|
991
|
+
const lineage = JSON.parse(row.lineage_json);
|
|
992
|
+
if (!Array.isArray(lineage) || !lineage.every((v) => typeof v === 'string'))
|
|
993
|
+
throw new Error('Unreadable Agent admission lineage');
|
|
994
|
+
ledgerIds.add(row.id);
|
|
995
|
+
for (const sid of lineage)
|
|
996
|
+
ledgerIds.add(sid);
|
|
755
997
|
}
|
|
756
998
|
}
|
|
757
999
|
}
|
|
@@ -769,12 +1011,13 @@ function sessionSources(db, cols, ids) {
|
|
|
769
1011
|
rememberPolicy(owner, policy);
|
|
770
1012
|
}
|
|
771
1013
|
}
|
|
772
|
-
const kanban = new Set(kanbanOwnedIds(db, cols,
|
|
1014
|
+
const kanban = new Set(kanbanOwnedIds(db, cols, new Map([...lineages].filter(([id]) => rows.has(id)))));
|
|
773
1015
|
for (const id of ids) {
|
|
774
1016
|
const row = rows.get(id);
|
|
775
1017
|
let source = kanban.has(id) ? 'kanban' : str(row?.source);
|
|
776
1018
|
let canonical = false;
|
|
777
1019
|
let retiredId = false;
|
|
1020
|
+
let mode;
|
|
778
1021
|
for (const segment of lineages.get(id) ?? [id]) {
|
|
779
1022
|
const chatId = str(rows.get(segment)?.chat_id);
|
|
780
1023
|
for (const owner of new Set([segment, chatId, receiptOwners.get(segment)].filter((v) => Boolean(v)))) {
|
|
@@ -788,8 +1031,12 @@ function sessionSources(db, cols, ids) {
|
|
|
788
1031
|
// A broken receipt never releases its owner or a physical row naming that owner.
|
|
789
1032
|
canonical = true;
|
|
790
1033
|
const origin = policy?.policy;
|
|
791
|
-
if (origin && typeof origin === 'object' && !Array.isArray(origin)
|
|
792
|
-
|
|
1034
|
+
if (origin && typeof origin === 'object' && !Array.isArray(origin)) {
|
|
1035
|
+
const frozen = origin;
|
|
1036
|
+
if (str(frozen.source))
|
|
1037
|
+
source = str(frozen.source);
|
|
1038
|
+
mode ??= frozen.safe_mode === true ? 'safe' : frozen.ignore_user_config === true ? 'ignore_config' : 'normal';
|
|
1039
|
+
}
|
|
793
1040
|
}
|
|
794
1041
|
}
|
|
795
1042
|
const authority = canonical;
|
|
@@ -798,10 +1045,38 @@ function sessionSources(db, cols, ids) {
|
|
|
798
1045
|
canonical = true;
|
|
799
1046
|
if (!row && !canonical && !retiredId)
|
|
800
1047
|
continue;
|
|
801
|
-
|
|
1048
|
+
const owned = { source, canonical: true, ...(mode ? { execution_mode: mode } : {}) };
|
|
1049
|
+
sources.set(id, retiredId && !authority ? { ...owned, reason: RETIRED_READ_ONLY_REASON } : canonical ? owned : source);
|
|
802
1050
|
}
|
|
803
1051
|
return sources;
|
|
804
1052
|
}
|
|
1053
|
+
/** TAL-1040: the request-id prefix Talaria mints for the diagnostic chats it asks an owner to create; its receipt keeps it. */
|
|
1054
|
+
export const WEB_DIAGNOSTIC_REQUEST_PREFIX = 'talaria-web:';
|
|
1055
|
+
/** Owner ids whose creation receipt carries Talaria's request prefix: chats Web started, shown whatever the external-session setting. */
|
|
1056
|
+
export function stateDbWebDiagnosticIds(dbPath) {
|
|
1057
|
+
if (!existsSync(dbPath))
|
|
1058
|
+
return [];
|
|
1059
|
+
let db;
|
|
1060
|
+
try {
|
|
1061
|
+
db = openStateDbReadonly(dbPath);
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return [];
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
if (!tableColumns(db, 'state_meta').has('value'))
|
|
1068
|
+
return [];
|
|
1069
|
+
const rows = db.prepare("SELECT key FROM state_meta WHERE key GLOB ? AND json_valid(value) AND json_extract(value, '$.request_id') GLOB ?")
|
|
1070
|
+
.all(`${LOCAL_POLICY_PREFIX}*`, `${WEB_DIAGNOSTIC_REQUEST_PREFIX}*`);
|
|
1071
|
+
return rows.map((row) => row.key.slice(LOCAL_POLICY_PREFIX.length));
|
|
1072
|
+
}
|
|
1073
|
+
catch {
|
|
1074
|
+
return [];
|
|
1075
|
+
}
|
|
1076
|
+
finally {
|
|
1077
|
+
finishStateDbRead(db);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
805
1080
|
/** Batched source/ownership probe. An unreadable authority answers null so callers fail closed. */
|
|
806
1081
|
export function stateDbSessionSources(dbPath, sessionIds) {
|
|
807
1082
|
const ids = [...new Set([...sessionIds].map((s) => s.trim()).filter(Boolean))];
|
|
@@ -815,14 +1090,13 @@ export function stateDbSessionSources(dbPath, sessionIds) {
|
|
|
815
1090
|
return null;
|
|
816
1091
|
}
|
|
817
1092
|
try {
|
|
818
|
-
db.exec('BEGIN');
|
|
819
1093
|
return sessionSources(db, tableColumns(db, 'sessions'), ids);
|
|
820
1094
|
}
|
|
821
1095
|
catch {
|
|
822
1096
|
return null;
|
|
823
1097
|
}
|
|
824
1098
|
finally {
|
|
825
|
-
db
|
|
1099
|
+
finishStateDbRead(db);
|
|
826
1100
|
}
|
|
827
1101
|
}
|
|
828
1102
|
/** The same marker parser serves conservative history boundaries and exact-parent ownership walks. */
|
|
@@ -851,8 +1125,8 @@ function isExplicitForkChild(row, includeReset) {
|
|
|
851
1125
|
}
|
|
852
1126
|
/**
|
|
853
1127
|
* Python `_resume_lineage_ids`: `sid` and the compression ancestors a resume loads, root first. Each hop's parent ended
|
|
854
|
-
* `compression` and the child is no branch, reset, delegate or tool fork. The walk stops at a cycle or
|
|
855
|
-
*
|
|
1128
|
+
* `compression` and the child is no branch, reset, delegate or tool fork. The walk stops at a cycle or
|
|
1129
|
+
* `RESUME_LINEAGE_CAP` ids; without the lineage columns it is `[sid]`.
|
|
856
1130
|
*/
|
|
857
1131
|
function resumeLineageWalker(db, cols) {
|
|
858
1132
|
if (!['parent_session_id', 'end_reason'].every((c) => cols.has(c)))
|
|
@@ -863,7 +1137,7 @@ function resumeLineageWalker(db, cols) {
|
|
|
863
1137
|
let current = select.get(sid);
|
|
864
1138
|
if (!current || isExplicitForkChild(current, false))
|
|
865
1139
|
return chain;
|
|
866
|
-
while (chain.length <
|
|
1140
|
+
while (chain.length < RESUME_LINEAGE_CAP) {
|
|
867
1141
|
const parentId = str(current.parent_session_id);
|
|
868
1142
|
if (!parentId || chain.includes(parentId) || isExplicitForkChild(current, true))
|
|
869
1143
|
break;
|
|
@@ -880,12 +1154,10 @@ function resumeLineageWalker(db, cols) {
|
|
|
880
1154
|
* TAL-793 (Agent `is_kanban_owned_session`): the `ids` whose compression lineage has a `kanban` `source` or
|
|
881
1155
|
* `created_source` (schema v31; `source` alone decides before it), a Kanban worker's transcript only its dispatcher continues.
|
|
882
1156
|
*/
|
|
883
|
-
function kanbanOwnedIds(db, cols,
|
|
884
|
-
if (!
|
|
1157
|
+
function kanbanOwnedIds(db, cols, lineages) {
|
|
1158
|
+
if (!lineages.size)
|
|
885
1159
|
return [];
|
|
886
|
-
const
|
|
887
|
-
const lineages = ids.map(walk);
|
|
888
|
-
const segments = [...new Set(lineages.flat())];
|
|
1160
|
+
const segments = [...new Set([...lineages.values()].flat())];
|
|
889
1161
|
const match = cols.has('created_source') ? "(source = 'kanban' OR created_source = 'kanban')" : "source = 'kanban'";
|
|
890
1162
|
const kanban = new Set();
|
|
891
1163
|
for (let i = 0; i < segments.length; i += 500) {
|
|
@@ -893,7 +1165,7 @@ function kanbanOwnedIds(db, cols, ids) {
|
|
|
893
1165
|
for (const row of db.prepare(`SELECT id FROM sessions WHERE id IN (${chunk.map(() => '?').join(',')}) AND ${match}`).all(...chunk))
|
|
894
1166
|
kanban.add(row.id);
|
|
895
1167
|
}
|
|
896
|
-
return
|
|
1168
|
+
return [...lineages].filter(([, lineage]) => lineage.some((sid) => kanban.has(sid))).map(([id]) => id);
|
|
897
1169
|
}
|
|
898
1170
|
/** Python `state_db_has_session`: true only when `sid` is a row of the sessions table; a missing or unreadable db is false. */
|
|
899
1171
|
export function stateDbHasSession(dbPath, sid) {
|
|
@@ -916,7 +1188,33 @@ export function stateDbHasSession(dbPath, sid) {
|
|
|
916
1188
|
return false;
|
|
917
1189
|
}
|
|
918
1190
|
finally {
|
|
919
|
-
db
|
|
1191
|
+
finishStateDbRead(db);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
/** True when a session has a live message; never materializes its transcript or compaction archive. */
|
|
1195
|
+
export function stateDbSessionHasMessages(dbPath, sid) {
|
|
1196
|
+
const id = sid.trim();
|
|
1197
|
+
if (!id || !existsSync(dbPath))
|
|
1198
|
+
return false;
|
|
1199
|
+
let db;
|
|
1200
|
+
try {
|
|
1201
|
+
db = openStateDbReadonly(dbPath);
|
|
1202
|
+
}
|
|
1203
|
+
catch {
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
try {
|
|
1207
|
+
const cols = tableColumns(db, 'messages');
|
|
1208
|
+
if (!cols.has('session_id'))
|
|
1209
|
+
return false;
|
|
1210
|
+
const active = cols.has('active') ? ' AND (active IS NULL OR active != 0)' : '';
|
|
1211
|
+
return db.prepare(`SELECT 1 FROM messages WHERE session_id = ?${active} LIMIT 1`).get(id) !== undefined;
|
|
1212
|
+
}
|
|
1213
|
+
catch {
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
finally {
|
|
1217
|
+
finishStateDbRead(db);
|
|
920
1218
|
}
|
|
921
1219
|
}
|
|
922
1220
|
/**
|
|
@@ -939,7 +1237,7 @@ export function cheapChangeFingerprint(dbPath, onError) {
|
|
|
939
1237
|
}
|
|
940
1238
|
}
|
|
941
1239
|
finally {
|
|
942
|
-
db
|
|
1240
|
+
finishStateDbRead(db);
|
|
943
1241
|
}
|
|
944
1242
|
for (const path of [dbPath, `${dbPath}-wal`]) {
|
|
945
1243
|
try {
|
|
@@ -971,22 +1269,28 @@ export function cheapChangeFingerprint(dbPath, onError) {
|
|
|
971
1269
|
return null;
|
|
972
1270
|
}
|
|
973
1271
|
}
|
|
974
|
-
|
|
1272
|
+
const STATE_DB_WORKER_LANES = ['listing', 'authority', 'session'];
|
|
1273
|
+
const listingWorkers = new Map();
|
|
1274
|
+
const listingReadScope = new AsyncLocalStorage();
|
|
1275
|
+
/** Keep a sidebar phase's nested reads on its lane without changing interactive callers' defaults. */
|
|
1276
|
+
export function withStateDbListingReads(read) { return listingReadScope.run('listing', read); }
|
|
975
1277
|
let nextCallId = 0;
|
|
976
|
-
/** Set
|
|
1278
|
+
/** Set until every worker exits during shutdown; calls then reject instead of starting another worker. */
|
|
977
1279
|
let stoppingWorker = false;
|
|
978
1280
|
/** Reject every call still waiting on `current` and forget it, so the next call starts a fresh worker. */
|
|
979
1281
|
function retireListingWorker(current, error) {
|
|
980
|
-
|
|
981
|
-
|
|
1282
|
+
for (const [lane, worker] of listingWorkers)
|
|
1283
|
+
if (worker === current)
|
|
1284
|
+
listingWorkers.delete(lane);
|
|
982
1285
|
for (const call of current.pending.values())
|
|
983
1286
|
call.reject(error);
|
|
984
1287
|
current.pending.clear();
|
|
985
1288
|
}
|
|
986
|
-
/**
|
|
987
|
-
function startedListingWorker() {
|
|
988
|
-
|
|
989
|
-
|
|
1289
|
+
/** Each lane lazily starts one long-lived worker and keeps its own FIFO and read-only handle cache. */
|
|
1290
|
+
function startedListingWorker(lane) {
|
|
1291
|
+
const existing = listingWorkers.get(lane);
|
|
1292
|
+
if (existing)
|
|
1293
|
+
return existing;
|
|
990
1294
|
// Source runs (vitest) load the TypeScript worker; the build runs the compiled one beside this module.
|
|
991
1295
|
const ext = import.meta.url.endsWith('.ts') ? 'ts' : 'js';
|
|
992
1296
|
const current = { worker: new Worker(new URL(`./state-db-worker.${ext}`, import.meta.url)), pending: new Map() };
|
|
@@ -1002,20 +1306,26 @@ function startedListingWorker() {
|
|
|
1002
1306
|
});
|
|
1003
1307
|
current.worker.on('error', (error) => { retireListingWorker(current, error); });
|
|
1004
1308
|
current.worker.on('exit', (code) => { retireListingWorker(current, new Error(`state.db worker exited with code ${String(code)}`)); });
|
|
1005
|
-
|
|
1309
|
+
listingWorkers.set(lane, current);
|
|
1006
1310
|
return current;
|
|
1007
1311
|
}
|
|
1008
|
-
function callListingWorker(call) {
|
|
1312
|
+
function callListingWorker(call, lane) {
|
|
1009
1313
|
// A multi-read caller the stop interrupted goes on to its next read; that read must not restart the worker.
|
|
1010
1314
|
if (stoppingWorker)
|
|
1011
1315
|
return Promise.reject(new Error('state.db worker stopped'));
|
|
1012
|
-
const
|
|
1316
|
+
const target = lane ?? listingReadScope.getStore() ?? (call.op === 'session' ? SESSION_READ_LANES[call.read] : call.op === 'sources' || call.op === 'existing' ? 'authority' : 'listing');
|
|
1317
|
+
const current = startedListingWorker(target);
|
|
1013
1318
|
const id = ++nextCallId;
|
|
1319
|
+
const timing = startupTimingScope.getStore();
|
|
1014
1320
|
return new Promise((resolve, reject) => {
|
|
1015
|
-
current.pending.set(id, { resolve
|
|
1321
|
+
current.pending.set(id, { resolve: (reply) => {
|
|
1322
|
+
if (reply.timing)
|
|
1323
|
+
timing?.workerRead(target, call.op === 'session' ? call.read : call.op, reply.timing);
|
|
1324
|
+
resolve(reply);
|
|
1325
|
+
}, reject });
|
|
1016
1326
|
// A call in flight keeps the process alive until it settles; an idle worker never does.
|
|
1017
1327
|
current.worker.ref();
|
|
1018
|
-
current.worker.postMessage({ ...call, id, delayMs: readDelayMs });
|
|
1328
|
+
current.worker.postMessage({ ...call, id, delayMs: readDelayMs, queuedAtNs: process.hrtime.bigint() });
|
|
1019
1329
|
});
|
|
1020
1330
|
}
|
|
1021
1331
|
/** `readImportableAgentSessionRows` on the listing worker, so a slow `state.db` never blocks the event loop. Rejects when the worker fails. */
|
|
@@ -1028,6 +1338,15 @@ export async function readImportableAgentSessionRowsAsync(dbPath, opts = {}) {
|
|
|
1028
1338
|
throw new Error(reply.error);
|
|
1029
1339
|
return reply.value;
|
|
1030
1340
|
}
|
|
1341
|
+
/** One worker crossing for all sidebar windows and their storage health. */
|
|
1342
|
+
export async function readImportableAgentSessionRowBatchesAsync(dbPath, queries) {
|
|
1343
|
+
const reply = await callListingWorker({ op: 'rowBatches', dbPath, queries: queries.map((opts) => ({ ...opts, log: undefined })) });
|
|
1344
|
+
for (const line of reply.logs)
|
|
1345
|
+
queries[0]?.log?.(line);
|
|
1346
|
+
if (reply.error !== undefined)
|
|
1347
|
+
throw new Error(reply.error);
|
|
1348
|
+
return reply.value;
|
|
1349
|
+
}
|
|
1031
1350
|
/** `cheapChangeFingerprint` on the listing worker; a worker failure reports as a failed change check. */
|
|
1032
1351
|
export async function cheapChangeFingerprintAsync(dbPath, onError) {
|
|
1033
1352
|
let reply;
|
|
@@ -1051,8 +1370,10 @@ export async function stateDbStorageAsync(dbPath) {
|
|
|
1051
1370
|
return stateDbReadFailure(error);
|
|
1052
1371
|
}
|
|
1053
1372
|
}
|
|
1054
|
-
/** `stateDbSessionSources` on the
|
|
1373
|
+
/** `stateDbSessionSources` on the authority worker; a worker failure is an unreadable state.db (null), so owners stay locked. Without a state.db it answers here, opening nothing. */
|
|
1055
1374
|
export async function stateDbSessionSourcesAsync(dbPath, sessionIds) {
|
|
1375
|
+
if (!existsSync(dbPath))
|
|
1376
|
+
return stateDbSessionSources(dbPath, sessionIds);
|
|
1056
1377
|
try {
|
|
1057
1378
|
return (await callListingWorker({ op: 'sources', dbPath, ids: [...sessionIds] })).value;
|
|
1058
1379
|
}
|
|
@@ -1060,7 +1381,16 @@ export async function stateDbSessionSourcesAsync(dbPath, sessionIds) {
|
|
|
1060
1381
|
return null;
|
|
1061
1382
|
}
|
|
1062
1383
|
}
|
|
1063
|
-
/** `
|
|
1384
|
+
/** `stateDbWebDiagnosticIds` on the listing worker; a failed read lists none, so the setting still governs every other row. */
|
|
1385
|
+
export async function stateDbWebDiagnosticIdsAsync(dbPath) {
|
|
1386
|
+
try {
|
|
1387
|
+
return (await callListingWorker({ op: 'webDiagnostic', dbPath })).value;
|
|
1388
|
+
}
|
|
1389
|
+
catch {
|
|
1390
|
+
return [];
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/** `agentSessionRowsExisting` on the authority worker; a worker failure assumes every row present, so nothing is pruned. */
|
|
1064
1394
|
export async function agentSessionRowsExistingAsync(dbPath, sessionIds) {
|
|
1065
1395
|
const ids = [...sessionIds];
|
|
1066
1396
|
try {
|
|
@@ -1070,19 +1400,60 @@ export async function agentSessionRowsExistingAsync(dbPath, sessionIds) {
|
|
|
1070
1400
|
return new Set(ids.map((s) => s.trim()).filter(Boolean));
|
|
1071
1401
|
}
|
|
1072
1402
|
}
|
|
1073
|
-
/**
|
|
1074
|
-
|
|
1075
|
-
|
|
1403
|
+
/**
|
|
1404
|
+
* TAL-923: the single-session readers behind chat start, session detail, and the mutation gates, run on the worker.
|
|
1405
|
+
* TAL-1162: and the transcript reads behind turn history, settlement, and the detail's page tail.
|
|
1406
|
+
* TAL-1256: shared cron admission identities and completion links use this same dispatcher.
|
|
1407
|
+
*/
|
|
1408
|
+
export const SESSION_READS = { stateDbHasSession, stateDbSessionHasMessages, insightsSessionRows, countConversationRounds, stateDbTailToolContent, stateDbLineageReport, stateDbCronJobIds, latestCronSessionInfo, stateDbLocalTargetRead, stateDbSessionRow, stateDbSessionMessages, stateDbCompressionLineage, stateDbSessionRead, stateDbSessionTailRead, stateDbSettlementRead };
|
|
1409
|
+
/** Exhaustive policy: authority never waits for transcript materialization; sidebar-scoped probes stay on listing. */
|
|
1410
|
+
const SESSION_READ_LANES = {
|
|
1411
|
+
stateDbHasSession: 'authority', stateDbSessionHasMessages: 'authority', stateDbLocalTargetRead: 'authority', stateDbSessionRow: 'authority',
|
|
1412
|
+
insightsSessionRows: 'session', countConversationRounds: 'session', stateDbTailToolContent: 'session', stateDbLineageReport: 'session',
|
|
1413
|
+
stateDbCronJobIds: 'session', latestCronSessionInfo: 'session', stateDbSessionMessages: 'session', stateDbCompressionLineage: 'session',
|
|
1414
|
+
stateDbSessionRead: 'session', stateDbSessionTailRead: 'session', stateDbSettlementRead: 'session',
|
|
1415
|
+
};
|
|
1416
|
+
/**
|
|
1417
|
+
* A `SESSION_READS` reader on its authority or transcript lane, so expensive reads cannot hold up authority. Each reader
|
|
1418
|
+
* already answers a failed read with `fallback`'s value; a worker failure answers it too. Without a state.db the reader
|
|
1419
|
+
* answers here, as it opens nothing then.
|
|
1420
|
+
*/
|
|
1421
|
+
export async function stateDbSessionReadAsync(fallback, read, ...args) {
|
|
1422
|
+
if (!existsSync(args[0]))
|
|
1423
|
+
return SESSION_READS[read](...args);
|
|
1424
|
+
try {
|
|
1425
|
+
const reply = await callListingWorker({ op: 'session', read, args });
|
|
1426
|
+
return reply.error === undefined ? reply.value : fallback;
|
|
1427
|
+
}
|
|
1428
|
+
catch {
|
|
1429
|
+
return fallback;
|
|
1430
|
+
}
|
|
1076
1431
|
}
|
|
1077
|
-
/**
|
|
1432
|
+
/** Test hook: crash one lane, or all when omitted, while its calls are in flight. */
|
|
1433
|
+
export function crashStateDbWorkerForTests(lane) {
|
|
1434
|
+
return Promise.all((lane ? [lane] : STATE_DB_WORKER_LANES).map((target) => callListingWorker({ op: 'crash' }, target)));
|
|
1435
|
+
}
|
|
1436
|
+
/** Stop every worker (server shutdown). Calls in flight reject; later calls start fresh workers. */
|
|
1078
1437
|
export async function stopStateDbWorker() {
|
|
1079
|
-
|
|
1080
|
-
|
|
1438
|
+
closeStateDbs();
|
|
1439
|
+
const workers = [...listingWorkers.values()];
|
|
1440
|
+
if (!workers.length)
|
|
1081
1441
|
return;
|
|
1082
1442
|
stoppingWorker = true;
|
|
1083
1443
|
try {
|
|
1084
|
-
|
|
1085
|
-
|
|
1444
|
+
await Promise.all(workers.map(async (current) => {
|
|
1445
|
+
retireListingWorker(current, new Error('state.db worker stopped'));
|
|
1446
|
+
const exited = new Promise((resolve) => { current.worker.once('exit', () => { resolve(); }); });
|
|
1447
|
+
// Idle workers close their cache through process.exit; a blocked read still cannot delay shutdown.
|
|
1448
|
+
const force = setTimeout(() => { void current.worker.terminate(); }, 100);
|
|
1449
|
+
try {
|
|
1450
|
+
current.worker.postMessage({ op: 'stop', id: 0, delayMs: 0 });
|
|
1451
|
+
await exited;
|
|
1452
|
+
}
|
|
1453
|
+
finally {
|
|
1454
|
+
clearTimeout(force);
|
|
1455
|
+
}
|
|
1456
|
+
}));
|
|
1086
1457
|
}
|
|
1087
1458
|
finally {
|
|
1088
1459
|
stoppingWorker = false;
|
|
@@ -1091,7 +1462,7 @@ export async function stopStateDbWorker() {
|
|
|
1091
1462
|
// ── message reader ────────────────────────────────────────────────────────
|
|
1092
1463
|
const STATE_DB_CONTENT_JSON_PREFIX = '\0json:';
|
|
1093
1464
|
// `display_kind` / `display_metadata`: the Agent's own tag for rows it writes itself (steers, delivered notifications; TAL-371).
|
|
1094
|
-
const OPTIONAL_MESSAGE_COLUMNS = ['tool_call_id', 'tool_calls', 'tool_name', 'reasoning', 'reasoning_details', 'codex_reasoning_items', 'reasoning_content', 'codex_message_items', 'api_content', 'display_kind', 'display_metadata', 'absorbed_message_uids'];
|
|
1465
|
+
const OPTIONAL_MESSAGE_COLUMNS = ['tool_call_id', 'tool_calls', 'tool_name', 'reasoning', 'reasoning_details', 'codex_reasoning_items', 'reasoning_content', 'codex_message_items', 'api_content', 'display_kind', 'display_metadata', 'absorbed_message_uids', '_compressed_summary'];
|
|
1095
1466
|
const JSON_MESSAGE_COLUMNS = new Set(['tool_calls', 'reasoning_details', 'codex_reasoning_items', 'codex_message_items', 'display_metadata', 'absorbed_message_uids']);
|
|
1096
1467
|
/** Python `_decode_state_db_content`: the Agent's sentinel-prefixed structured content, left untouched otherwise. */
|
|
1097
1468
|
function decodeStateDbContent(value) {
|
|
@@ -1123,11 +1494,28 @@ function jsonLoadsIfString(value) {
|
|
|
1123
1494
|
return value;
|
|
1124
1495
|
}
|
|
1125
1496
|
}
|
|
1497
|
+
// Agent v0.22.3's ordinary kept-tail handoff. Only its explicit flag and exact wrapper prove a carrier.
|
|
1498
|
+
const PRIOR_CONTEXT_HEADER = '[PRIOR CONTEXT — for reference only; not a new message]';
|
|
1499
|
+
const COMPACTION_SUMMARY_DELIMITER = '\n\n[END OF PRIOR CONTEXT — COMPACTION SUMMARY BELOW]\n\n';
|
|
1500
|
+
function compactionCarrier(row) {
|
|
1501
|
+
if (row._compressed_summary !== 1 && row._compressed_summary !== true)
|
|
1502
|
+
return null;
|
|
1503
|
+
const content = decodeStateDbContent(row.content);
|
|
1504
|
+
if ((row.role !== 'user' && row.role !== 'assistant') || typeof content !== 'string' || !content.startsWith(`${PRIOR_CONTEXT_HEADER}\n`))
|
|
1505
|
+
return null;
|
|
1506
|
+
const boundary = content.indexOf(COMPACTION_SUMMARY_DELIMITER, PRIOR_CONTEXT_HEADER.length);
|
|
1507
|
+
if (boundary < 0)
|
|
1508
|
+
return null;
|
|
1509
|
+
// Remove only the separators the Agent added; preserve the kept reply's own whitespace.
|
|
1510
|
+
const prior = content.slice(PRIOR_CONTEXT_HEADER.length + 1, boundary);
|
|
1511
|
+
const summary = content.slice(boundary + COMPACTION_SUMMARY_DELIMITER.length).trim();
|
|
1512
|
+
return prior && summary ? { prior, summary } : null;
|
|
1513
|
+
}
|
|
1126
1514
|
/** Python `_project_state_db_message`: one row → WebUI message (`tool_name → name`, empty optionals omitted). */
|
|
1127
1515
|
function projectStateDbMessage(row, hasId) {
|
|
1128
|
-
const msg = { role: row.role, content: decodeStateDbContent(row.content), timestamp: row.timestamp };
|
|
1516
|
+
const msg = { role: row.role, content: compactionCarrier(row)?.prior ?? decodeStateDbContent(row.content), timestamp: row.timestamp };
|
|
1129
1517
|
for (const col of OPTIONAL_MESSAGE_COLUMNS) {
|
|
1130
|
-
if (!(col in row))
|
|
1518
|
+
if (col === '_compressed_summary' || !(col in row))
|
|
1131
1519
|
continue;
|
|
1132
1520
|
const value = row[col];
|
|
1133
1521
|
if (value === null || value === undefined || value === '')
|
|
@@ -1159,7 +1547,7 @@ function stateDbGeneration(dbPath) {
|
|
|
1159
1547
|
}
|
|
1160
1548
|
// Agent v0.21.6's exact in-flight request wrapper. Unknown rewrites are not copy evidence.
|
|
1161
1549
|
const INFLIGHT_REPLAY_HEADER = '[STILL IN PROGRESS — this is the active request, restated after the compaction boundary because it was not finished yet. Continue it; do not start over.]';
|
|
1162
|
-
/**
|
|
1550
|
+
/** Compare the kept payload after the Agent's exact request/carrier wrappers and stale api_content are removed. */
|
|
1163
1551
|
function replayPayload(row) {
|
|
1164
1552
|
const message = projectStateDbMessage(row, false);
|
|
1165
1553
|
delete message.timestamp;
|
|
@@ -1190,7 +1578,7 @@ function replayPayload(row) {
|
|
|
1190
1578
|
}
|
|
1191
1579
|
message.content = content;
|
|
1192
1580
|
delete message.api_content; // _reappend_inflight_user_task drops this stale model-facing sidecar.
|
|
1193
|
-
return { copyKey, replayKey: JSON.stringify(message), replay };
|
|
1581
|
+
return { copyKey, replayKey: JSON.stringify(message), replay, carrier: compactionCarrier(row)?.summary ?? null };
|
|
1194
1582
|
}
|
|
1195
1583
|
/**
|
|
1196
1584
|
* Compare adjacent generations, never all rows of one UID with its oldest archive. The archive retains
|
|
@@ -1215,13 +1603,15 @@ function replayIdentities(db, chain, rows, available) {
|
|
|
1215
1603
|
const payload = replayPayload(row);
|
|
1216
1604
|
const prior = previous.get(uid);
|
|
1217
1605
|
const copy = payload.copyKey === prior?.copyKey && row.timestamp === prior.timestamp;
|
|
1218
|
-
const restatement =
|
|
1606
|
+
const restatement = payload.replayKey === prior?.replayKey && ((payload.replay && (prior.active !== 0 || prior.compacted === 1)) ||
|
|
1607
|
+
(payload.carrier && row.timestamp === prior.timestamp));
|
|
1219
1608
|
const identity = prior && (copy || restatement) ? prior.identity : Number(row.id);
|
|
1220
1609
|
const changed = prior ? (copy || restatement ? prior.changed : true) : false;
|
|
1221
1610
|
const sourceKey = prior && (copy || restatement) ? prior.sourceKey : stateDbMessageFingerprint(projectStateDbMessage(row, false));
|
|
1611
|
+
const summaryId = payload.carrier && payload.carrier === prior?.carrier && row.timestamp === prior.timestamp ? prior.summaryId : Number(row.id);
|
|
1222
1612
|
if (prior && row.role !== 'tool' && liveIds.has(row.id))
|
|
1223
|
-
identities.set(Number(row.id), { id: identity, changed, sourceKey });
|
|
1224
|
-
previous.set(uid, { ...payload, identity, changed, sourceKey, timestamp: row.timestamp, active: row.active, compacted: row.compacted });
|
|
1613
|
+
identities.set(Number(row.id), { id: identity, changed, sourceKey, summaryId });
|
|
1614
|
+
previous.set(uid, { ...payload, identity, changed, sourceKey, summaryId, timestamp: row.timestamp, active: row.active, compacted: row.compacted });
|
|
1225
1615
|
}
|
|
1226
1616
|
return identities;
|
|
1227
1617
|
}
|
|
@@ -1235,6 +1625,16 @@ export function stateDbSessionMessages(dbPath, sid, opts = {}) {
|
|
|
1235
1625
|
export function stateDbSessionRead(dbPath, sid, opts = {}) {
|
|
1236
1626
|
return readStateDbSession(dbPath, sid, opts);
|
|
1237
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* A settlement's reads in one request: the transcript under `recorded` (the session's `state_db_lineage`), the lineage
|
|
1630
|
+
* the Agent's compression rotations give it now, and, when that differs from `recorded`, the transcript under it.
|
|
1631
|
+
*/
|
|
1632
|
+
export function stateDbSettlementRead(dbPath, sid, recorded) {
|
|
1633
|
+
const read = stateDbSessionRead(dbPath, sid, { stitch: false, lineage: recorded?.[0] === sid ? recorded : null });
|
|
1634
|
+
const lineage = stateDbCompressionLineage(dbPath, sid);
|
|
1635
|
+
const moved = JSON.stringify(lineage) !== JSON.stringify(recorded ?? [sid]);
|
|
1636
|
+
return { ...read, lineage, rotated: moved ? stateDbSessionRead(dbPath, sid, { stitch: false, lineage: lineage.length > 1 && lineage[0] === sid ? lineage : null }) : null };
|
|
1637
|
+
}
|
|
1238
1638
|
/** The same snapshot and provenance projection, restricted to physical rows past the seen marker. */
|
|
1239
1639
|
export function stateDbSessionTailRead(dbPath, sid, afterId, opts = {}) {
|
|
1240
1640
|
return readStateDbSession(dbPath, sid, { ...opts, stitch: false, afterId });
|
|
@@ -1248,11 +1648,10 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1248
1648
|
try {
|
|
1249
1649
|
db = openStateDbReadonly(dbPath);
|
|
1250
1650
|
}
|
|
1251
|
-
catch {
|
|
1252
|
-
return none;
|
|
1651
|
+
catch (error) {
|
|
1652
|
+
return { ...none, reason: stateDbReadFailure(error).reason };
|
|
1253
1653
|
}
|
|
1254
1654
|
try {
|
|
1255
|
-
db.exec('BEGIN'); // The live rows and their archived predecessors must describe one snapshot.
|
|
1256
1655
|
const available = tableColumns(db, 'messages');
|
|
1257
1656
|
if (!['role', 'content', 'timestamp'].every((c) => available.has(c)))
|
|
1258
1657
|
return { ...none, ok: available.size > 0 };
|
|
@@ -1288,7 +1687,7 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1288
1687
|
const rangeArgs = opts.afterId === undefined ? [] : [opts.afterId];
|
|
1289
1688
|
const order = hasId ? 'id' : 'timestamp';
|
|
1290
1689
|
const rows = db.prepare(`SELECT ${selected.join(', ')}, session_id FROM messages WHERE session_id IN (${chain.map(() => '?').join(', ')})${activeClause}${range} ORDER BY ${order} ASC`).all(...chain, ...rangeArgs);
|
|
1291
|
-
|
|
1690
|
+
let projected = rows.map((row) => projectStateDbMessage(row, hasId));
|
|
1292
1691
|
// TAL-709: an in-place compaction archives the live rows and re-inserts each as a copy that keeps its role and timestamp
|
|
1293
1692
|
// (content may be pruned). A copy takes the id of the oldest archived row it copies, so the merge sees a row it has
|
|
1294
1693
|
// already read instead of a new one. Only a compaction archive (`compacted = 1`) vouches for a changed copy: a rewound
|
|
@@ -1307,8 +1706,9 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1307
1706
|
projected[i]._state_db_row_id = twin;
|
|
1308
1707
|
}
|
|
1309
1708
|
}
|
|
1709
|
+
let identities;
|
|
1310
1710
|
if (hasId && ['message_uid', 'active', 'compacted'].every((c) => available.has(c))) {
|
|
1311
|
-
|
|
1711
|
+
identities = replayIdentities(db, local?.context ?? chain, rows, available);
|
|
1312
1712
|
for (const [i, row] of rows.entries()) {
|
|
1313
1713
|
const identity = identities.get(Number(row.id));
|
|
1314
1714
|
// A known changed generation overrides even a coincidental legacy timestamp twin.
|
|
@@ -1321,6 +1721,13 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1321
1721
|
}
|
|
1322
1722
|
}
|
|
1323
1723
|
}
|
|
1724
|
+
// The kept reply and summary have independent archive identities; neither can consume the other's copy.
|
|
1725
|
+
const withCarrierSummary = (row, message) => {
|
|
1726
|
+
const carrier = compactionCarrier(row);
|
|
1727
|
+
return carrier ? [message, { role: 'assistant', content: carrier.summary, timestamp: row.timestamp,
|
|
1728
|
+
_context_compression_marker: true, ...(hasId ? { _state_db_row_id: identities?.get(Number(row.id))?.summaryId ?? row.id } : {}) }] : [message];
|
|
1729
|
+
};
|
|
1730
|
+
projected = rows.flatMap((row, i) => withCarrierSummary(row, projected[i]));
|
|
1324
1731
|
// TAL-493: the database file's identity, so a marker taken in a replaced state.db is never applied to its successor.
|
|
1325
1732
|
const generation = stateDbGeneration(dbPath);
|
|
1326
1733
|
if (hasId) {
|
|
@@ -1333,7 +1740,7 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1333
1740
|
if (!anchor) {
|
|
1334
1741
|
const row = db.prepare(`SELECT ${selected.join(', ')}, session_id FROM messages WHERE session_id IN (${chain.map(() => '?').join(', ')}) AND id = ?${activeClause} LIMIT 1`).get(...chain, opts.afterId);
|
|
1335
1742
|
if (row)
|
|
1336
|
-
anchor = { ...projectStateDbMessage(row, true), _state_db_generation: generation };
|
|
1743
|
+
anchor = { ...withCarrierSummary(row, projectStateDbMessage(row, true)).at(-1), _state_db_generation: generation };
|
|
1337
1744
|
}
|
|
1338
1745
|
}
|
|
1339
1746
|
let requiresFullRead = false;
|
|
@@ -1352,7 +1759,7 @@ function readStateDbSession(dbPath, sid, opts) {
|
|
|
1352
1759
|
return { ...none, reason: stateDbReadFailure(error).reason };
|
|
1353
1760
|
}
|
|
1354
1761
|
finally {
|
|
1355
|
-
db
|
|
1762
|
+
finishStateDbRead(db);
|
|
1356
1763
|
}
|
|
1357
1764
|
}
|
|
1358
1765
|
/**
|
|
@@ -1372,7 +1779,6 @@ export function stateDbCompressionLineage(dbPath, sid) {
|
|
|
1372
1779
|
return lineage;
|
|
1373
1780
|
}
|
|
1374
1781
|
try {
|
|
1375
|
-
db.exec('BEGIN');
|
|
1376
1782
|
const local = localLineages(db, lineage[0]).get(lineage[0]);
|
|
1377
1783
|
if (local)
|
|
1378
1784
|
return [...local.ids];
|
|
@@ -1398,7 +1804,7 @@ export function stateDbCompressionLineage(dbPath, sid) {
|
|
|
1398
1804
|
return lineage.slice(0, 1);
|
|
1399
1805
|
}
|
|
1400
1806
|
finally {
|
|
1401
|
-
db
|
|
1807
|
+
finishStateDbRead(db);
|
|
1402
1808
|
}
|
|
1403
1809
|
}
|
|
1404
1810
|
function lineageReportRow(row, role) {
|
|
@@ -1408,7 +1814,7 @@ function lineageReportRow(row, role) {
|
|
|
1408
1814
|
end_reason: row.end_reason ?? null, active: ended === null, archived: false,
|
|
1409
1815
|
};
|
|
1410
1816
|
}
|
|
1411
|
-
const emptyLineageReport = (sid) => ({
|
|
1817
|
+
export const emptyLineageReport = (sid) => ({
|
|
1412
1818
|
mutation: false, found: false, session_id: sid, lineage_key: sid, tip_session_id: sid, total_segments: 0, materialized_segments: 0, segments: [], children: [], manual_review: false,
|
|
1413
1819
|
});
|
|
1414
1820
|
/**
|
|
@@ -1429,7 +1835,6 @@ export function stateDbLineageReport(dbPath, rawSid, maxHops = COMPRESSION_CHAIN
|
|
|
1429
1835
|
return emptyLineageReport(sid);
|
|
1430
1836
|
}
|
|
1431
1837
|
try {
|
|
1432
|
-
db.exec('BEGIN');
|
|
1433
1838
|
const local = localLineages(db, sid).get(sid);
|
|
1434
1839
|
const cols = tableColumns(db, 'sessions');
|
|
1435
1840
|
if (!['id', 'parent_session_id', 'end_reason'].every((c) => cols.has(c)))
|
|
@@ -1485,7 +1890,7 @@ export function stateDbLineageReport(dbPath, rawSid, maxHops = COMPRESSION_CHAIN
|
|
|
1485
1890
|
return emptyLineageReport(sid);
|
|
1486
1891
|
}
|
|
1487
1892
|
finally {
|
|
1488
|
-
db
|
|
1893
|
+
finishStateDbRead(db);
|
|
1489
1894
|
}
|
|
1490
1895
|
}
|
|
1491
1896
|
/** The `sessions` row for `sid` (source and lifecycle columns), or null. */
|
|
@@ -1511,7 +1916,7 @@ export function stateDbSessionRow(dbPath, sid) {
|
|
|
1511
1916
|
return null;
|
|
1512
1917
|
}
|
|
1513
1918
|
finally {
|
|
1514
|
-
db
|
|
1919
|
+
finishStateDbRead(db);
|
|
1515
1920
|
}
|
|
1516
1921
|
}
|
|
1517
1922
|
/**
|
|
@@ -1537,21 +1942,31 @@ export function insightsSessionRows(dbPath, cutoff) {
|
|
|
1537
1942
|
const col = (name) => (cols.has(name) ? `s.${name}` : 'NULL');
|
|
1538
1943
|
const messageCols = tableColumns(db, 'messages');
|
|
1539
1944
|
const hasMessages = messageCols.has('session_id') && messageCols.has('timestamp');
|
|
1540
|
-
|
|
1541
|
-
try {
|
|
1542
|
-
indexed = hasMessages && db.prepare('PRAGMA index_list(messages)').all().some((r) => r.name === 'idx_messages_session');
|
|
1543
|
-
}
|
|
1544
|
-
catch {
|
|
1545
|
-
indexed = false;
|
|
1546
|
-
}
|
|
1945
|
+
const indexed = hasMessages && tableIndexes(db, 'messages').has('idx_messages_session');
|
|
1547
1946
|
// Without the index a per-session lookup scans messages once per session; one grouped scan replaces it.
|
|
1548
|
-
const lastMessage = !hasMessages ? 'NULL' : indexed ?
|
|
1549
|
-
const
|
|
1947
|
+
const lastMessage = !hasMessages ? 'NULL' : indexed ? lastMessageTimestamp(indexed) : 'lm.last_message_at';
|
|
1948
|
+
const messageCte = hasMessages && !indexed ? 'latest_messages AS MATERIALIZED (SELECT session_id, MAX(timestamp) AS last_message_at FROM messages GROUP BY session_id)' : '';
|
|
1949
|
+
const join = messageCte ? 'LEFT JOIN latest_messages lm ON lm.session_id = s.id' : '';
|
|
1550
1950
|
const source = cols.has('source') ? "LOWER(COALESCE(s.source, '')) != 'webui'" : '1';
|
|
1551
|
-
const
|
|
1951
|
+
const activityIndexed = cols.has('last_activity_at') && tableIndexes(db, 'sessions').has('idx_sessions_effective_activity');
|
|
1952
|
+
const activity = activityIndexed ? freshestActivity('s.last_activity_at', `MAX(COALESCE(${col('ended_at')}, 0), COALESCE(${lastMessage}, 0))`)
|
|
1953
|
+
: `MAX(COALESCE(s.started_at, 0), COALESCE(${col('ended_at')}, 0), COALESCE(${lastMessage}, 0))`;
|
|
1954
|
+
// The heartbeat can lag a message or end time; union their candidates before the exact activity check.
|
|
1955
|
+
const candidates = activityIndexed ? [
|
|
1956
|
+
'SELECT s.id FROM sessions s INDEXED BY idx_sessions_effective_activity WHERE COALESCE(s.last_activity_at, s.started_at) >= ?',
|
|
1957
|
+
'SELECT id FROM sessions WHERE started_at >= ?',
|
|
1958
|
+
...(cols.has('ended_at') ? ['SELECT id FROM sessions WHERE ended_at >= ?'] : []),
|
|
1959
|
+
...(hasMessages ? [indexed ? 'SELECT DISTINCT session_id AS id FROM messages WHERE timestamp >= ?' : 'SELECT session_id AS id FROM latest_messages WHERE last_message_at >= ?'] : []),
|
|
1960
|
+
] : [];
|
|
1961
|
+
const ctes = [messageCte, ...(activityIndexed ? [`candidates AS MATERIALIZED (SELECT DISTINCT id FROM (${candidates.join(' UNION ALL ')}))`] : [])].filter(Boolean);
|
|
1962
|
+
const withClause = ctes.length ? `WITH ${ctes.join(', ')} ` : '';
|
|
1963
|
+
// CROSS JOIN keeps the candidate IDs ahead of the expensive per-session freshness projection.
|
|
1964
|
+
const from = activityIndexed ? 'candidates c CROSS JOIN sessions s ON s.id = c.id' : 'sessions s';
|
|
1965
|
+
const ordering = activityIndexed ? ' ORDER BY COALESCE(s.last_activity_at, s.started_at) DESC' : '';
|
|
1966
|
+
const rows = db.prepare(`${withClause}SELECT * FROM (SELECT s.id AS id, ${col('model')} AS model, ${col('message_count')} AS message_count, ${col('input_tokens')} AS input_tokens,
|
|
1552
1967
|
${col('output_tokens')} AS output_tokens, ${col('cache_read_tokens')} AS cache_read_tokens, ${col('estimated_cost_usd')} AS estimated_cost_usd, s.started_at AS started_at,
|
|
1553
|
-
|
|
1554
|
-
FROM
|
|
1968
|
+
${activity} AS last_activity
|
|
1969
|
+
FROM ${from} ${join} WHERE ${source}${ordering}) WHERE last_activity >= ?`).all(...Array(candidates.length + 1).fill(cutoff));
|
|
1555
1970
|
return rows.map((r) => ({
|
|
1556
1971
|
session_id: str(r.id), model: r.model, message_count: r.message_count, input_tokens: r.input_tokens, output_tokens: r.output_tokens,
|
|
1557
1972
|
cache_read_tokens: r.cache_read_tokens, estimated_cost: r.estimated_cost_usd, created_at: r.started_at, updated_at: r.last_activity,
|
|
@@ -1561,7 +1976,7 @@ export function insightsSessionRows(dbPath, cutoff) {
|
|
|
1561
1976
|
return [];
|
|
1562
1977
|
}
|
|
1563
1978
|
finally {
|
|
1564
|
-
db
|
|
1979
|
+
finishStateDbRead(db);
|
|
1565
1980
|
}
|
|
1566
1981
|
}
|
|
1567
1982
|
/** A state.db `timestamp` in unix seconds: a number, or an ISO-8601 string (Python `fromisoformat`); null when unreadable. */
|
|
@@ -1590,7 +2005,7 @@ export function countConversationRounds(dbPath, sid, since = null) {
|
|
|
1590
2005
|
rows = db.prepare('SELECT role, timestamp FROM messages WHERE session_id = ? ORDER BY timestamp ASC').all(sid);
|
|
1591
2006
|
}
|
|
1592
2007
|
finally {
|
|
1593
|
-
db
|
|
2008
|
+
finishStateDbRead(db);
|
|
1594
2009
|
}
|
|
1595
2010
|
}
|
|
1596
2011
|
catch {
|
|
@@ -1631,7 +2046,7 @@ export function stateDbTailToolContent(dbPath, sid) {
|
|
|
1631
2046
|
return typeof content === 'string' ? content : null;
|
|
1632
2047
|
}
|
|
1633
2048
|
finally {
|
|
1634
|
-
db
|
|
2049
|
+
finishStateDbRead(db);
|
|
1635
2050
|
}
|
|
1636
2051
|
}
|
|
1637
2052
|
catch {
|