@cloudcli-ai/cloudcli 1.37.1 → 1.37.3
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/README.md +1 -1
- package/dist/assets/abnfDiagram-N423BO3Z-CJntskwA.js +1 -0
- package/dist/assets/arc-CIfH4uMN.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
- package/dist/assets/channel-C5xU9jMw.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
- package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-BTDHQ6Id.css +32 -0
- package/dist/assets/index-sFfz8iBR.js +4931 -0
- package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-61vxjRnP.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
- package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
- package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
- package/dist/index.html +3 -3
- package/dist-server/server/index.js +8 -5
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +2 -2
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +1 -1
- package/dist-server/server/modules/database/connection.js.map +1 -1
- package/dist-server/server/modules/database/index.js +5 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +32 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +92 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +422 -86
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +29 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
- package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
- package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
- package/dist-server/server/modules/user/user.module.js +10 -1
- package/dist-server/server/modules/user/user.module.js.map +1 -1
- package/dist-server/server/modules/user/user.routes.js +44 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -1
- package/dist-server/server/modules/user/user.service.js +75 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -1
- package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +7 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
- package/dist-server/server/shared/claude-cli-path.js +22 -5
- package/dist-server/server/shared/claude-cli-path.js.map +1 -1
- package/dist-server/server/shared/index.js +3 -0
- package/dist-server/server/shared/index.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +42 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +23 -16
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +12 -6
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/agent/agent.routes.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +33 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -33
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/database/connection.ts +2 -1
- package/server/modules/database/index.ts +10 -0
- package/server/modules/database/migrations.ts +37 -0
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +196 -3
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/schema.ts +96 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.service.ts +21 -10
- package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/projects/services/project-delete.service.ts +13 -1
- package/server/modules/projects/services/project-star.service.ts +2 -2
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
- package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
- package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
- package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
- package/server/modules/providers/provider.routes.ts +36 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
- package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
- package/server/modules/providers/services/session-history-cache.service.ts +139 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
- package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
- package/server/modules/providers/services/sessions.service.ts +201 -9
- package/server/modules/providers/tests/claude-auth.test.ts +58 -0
- package/server/modules/providers/tests/claude-models.test.ts +87 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
- package/server/modules/providers/tests/opencode-models.test.ts +166 -8
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
- package/server/modules/providers/tests/provider.routes.test.ts +61 -0
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions.service.test.ts +67 -1
- package/server/modules/providers/tests/skills.test.ts +21 -5
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
- package/server/modules/user/tests/user.service.test.ts +90 -0
- package/server/modules/user/user.module.ts +10 -1
- package/server/modules/user/user.routes.ts +44 -0
- package/server/modules/user/user.service.ts +105 -0
- package/server/modules/voice/tests/voice.service.test.ts +3 -2
- package/server/modules/websocket/index.ts +8 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
- package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
- package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
- package/server/shared/claude-cli-path.ts +24 -6
- package/server/shared/index.ts +3 -0
- package/server/shared/interfaces.ts +62 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/claude-cli-path.test.ts +58 -1
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/types.ts +123 -3
- package/server/shared/utils.ts +50 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-D3jfzuKN.css +0 -32
- package/dist/assets/index-DTMWjJj8.js +0 -1550
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -11,6 +11,12 @@ import { AppError, getOpenCodeDatabasePath } from '@/shared/utils.js';
|
|
|
11
11
|
|
|
12
12
|
type SessionRow = NonNullable<ReturnType<typeof sessionsDb.getSessionById>>;
|
|
13
13
|
|
|
14
|
+
type FileTail = {
|
|
15
|
+
content: string;
|
|
16
|
+
/** True when `content` is the whole file, not just its trailing bytes. */
|
|
17
|
+
isComplete: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
14
20
|
type ProviderTokenUsageServiceDependencies = {
|
|
15
21
|
getSessionById: (sessionId: string) => SessionRow | null | undefined;
|
|
16
22
|
getHomeDirectory: () => string;
|
|
@@ -18,7 +24,9 @@ type ProviderTokenUsageServiceDependencies = {
|
|
|
18
24
|
fileExists: (filePath: string) => boolean;
|
|
19
25
|
readDirectory: (directoryPath: string) => Promise<Dirent[]>;
|
|
20
26
|
readTextFile: (filePath: string) => Promise<string>;
|
|
27
|
+
readTextFileTail: (filePath: string, maxBytes: number) => Promise<FileTail>;
|
|
21
28
|
getClaudeContextWindow: () => string | undefined;
|
|
29
|
+
isProviderSessionSuperseded: (providerSessionId: string, provider: string) => boolean;
|
|
22
30
|
};
|
|
23
31
|
|
|
24
32
|
type TokenUsageResult = {
|
|
@@ -45,6 +53,14 @@ type OpenCodeTokenRow = {
|
|
|
45
53
|
cacheWriteTokens: number | null;
|
|
46
54
|
};
|
|
47
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Both JSONL usage readers below only need the newest usage row, which sits at
|
|
58
|
+
* or near the end of the transcript. Reading just this much of the tail keeps
|
|
59
|
+
* a session-open usage lookup O(1) in the transcript's size; the rare tail
|
|
60
|
+
* with no usage row at all falls back to the full read.
|
|
61
|
+
*/
|
|
62
|
+
const TOKEN_USAGE_TAIL_BYTES = 4 * 1024 * 1024;
|
|
63
|
+
|
|
48
64
|
const defaultDependencies: ProviderTokenUsageServiceDependencies = {
|
|
49
65
|
getSessionById: (sessionId) => sessionsDb.getSessionById(sessionId),
|
|
50
66
|
getHomeDirectory: () => os.homedir(),
|
|
@@ -52,7 +68,32 @@ const defaultDependencies: ProviderTokenUsageServiceDependencies = {
|
|
|
52
68
|
fileExists: (filePath) => fsSync.existsSync(filePath),
|
|
53
69
|
readDirectory: (directoryPath) => fsp.readdir(directoryPath, { withFileTypes: true }),
|
|
54
70
|
readTextFile: (filePath) => fsp.readFile(filePath, 'utf8'),
|
|
71
|
+
readTextFileTail: async (filePath, maxBytes) => {
|
|
72
|
+
const handle = await fsp.open(filePath, 'r');
|
|
73
|
+
try {
|
|
74
|
+
const { size } = await handle.stat();
|
|
75
|
+
if (size <= maxBytes) {
|
|
76
|
+
return { content: await handle.readFile({ encoding: 'utf8' }), isComplete: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
80
|
+
await handle.read(buffer, 0, maxBytes, size - maxBytes);
|
|
81
|
+
const content = buffer.toString('utf8');
|
|
82
|
+
// The window almost never starts on a row boundary; dropping everything
|
|
83
|
+
// up to the first newline discards the partial row (and any split
|
|
84
|
+
// multi-byte character with it).
|
|
85
|
+
const firstNewline = content.indexOf('\n');
|
|
86
|
+
return {
|
|
87
|
+
content: firstNewline === -1 ? '' : content.slice(firstNewline + 1),
|
|
88
|
+
isComplete: false,
|
|
89
|
+
};
|
|
90
|
+
} finally {
|
|
91
|
+
await handle.close();
|
|
92
|
+
}
|
|
93
|
+
},
|
|
55
94
|
getClaudeContextWindow: () => process.env.CONTEXT_WINDOW,
|
|
95
|
+
isProviderSessionSuperseded: (providerSessionId, provider) =>
|
|
96
|
+
sessionsDb.isProviderSessionSuperseded(providerSessionId, provider),
|
|
56
97
|
};
|
|
57
98
|
|
|
58
99
|
function readUsageNumber(value: unknown): number {
|
|
@@ -92,11 +133,8 @@ async function findCodexSessionFile(
|
|
|
92
133
|
return null;
|
|
93
134
|
}
|
|
94
135
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let outputTokens = 0;
|
|
98
|
-
let totalTokens = 0;
|
|
99
|
-
let contextWindow = 200_000;
|
|
136
|
+
/** Newest `token_count` snapshot in the given JSONL text, or null when it has none. */
|
|
137
|
+
function findCodexTokenUsage(fileContent: string): TokenUsageResult | null {
|
|
100
138
|
const lines = fileContent.trim().split('\n');
|
|
101
139
|
|
|
102
140
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
@@ -109,58 +147,101 @@ function readCodexTokenUsage(fileContent: string): TokenUsageResult {
|
|
|
109
147
|
continue;
|
|
110
148
|
}
|
|
111
149
|
|
|
150
|
+
let inputTokens = 0;
|
|
151
|
+
let outputTokens = 0;
|
|
152
|
+
let totalTokens = 0;
|
|
112
153
|
if (tokenInfo.total_token_usage) {
|
|
113
154
|
inputTokens = readUsageNumber(tokenInfo.total_token_usage.input_tokens);
|
|
114
155
|
outputTokens = readUsageNumber(tokenInfo.total_token_usage.output_tokens);
|
|
115
156
|
totalTokens = readUsageNumber(tokenInfo.total_token_usage.total_tokens)
|
|
116
157
|
|| inputTokens + outputTokens;
|
|
117
158
|
}
|
|
118
|
-
|
|
119
|
-
|
|
159
|
+
return {
|
|
160
|
+
used: totalTokens,
|
|
161
|
+
total: readUsageNumber(tokenInfo.model_context_window) || 200_000,
|
|
162
|
+
inputTokens,
|
|
163
|
+
outputTokens,
|
|
164
|
+
breakdown: { input: inputTokens, output: outputTokens },
|
|
165
|
+
};
|
|
120
166
|
} catch {
|
|
121
167
|
// A provider may be writing the last JSONL line while this read happens.
|
|
122
168
|
}
|
|
123
169
|
}
|
|
124
170
|
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function emptyCodexTokenUsage(): TokenUsageResult {
|
|
125
175
|
return {
|
|
126
|
-
used:
|
|
127
|
-
total:
|
|
128
|
-
inputTokens,
|
|
129
|
-
outputTokens,
|
|
130
|
-
breakdown: { input:
|
|
176
|
+
used: 0,
|
|
177
|
+
total: 200_000,
|
|
178
|
+
inputTokens: 0,
|
|
179
|
+
outputTokens: 0,
|
|
180
|
+
breakdown: { input: 0, output: 0 },
|
|
131
181
|
};
|
|
132
182
|
}
|
|
133
183
|
|
|
134
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Latest context-window usage from a Claude transcript's already-parsed rows.
|
|
186
|
+
*
|
|
187
|
+
* Exported because the session-messages reader hands the same usage back on
|
|
188
|
+
* every history page, the way the Codex and OpenCode readers do. Without that,
|
|
189
|
+
* a Claude session's counter only moved when the session was reselected, and
|
|
190
|
+
* the store's "this provider reports no usage" path overwrote it with zero.
|
|
191
|
+
*
|
|
192
|
+
* Reads the newest assistant turn only: `input_tokens + cache_read +
|
|
193
|
+
* cache_creation` is that one request's whole prompt, i.e. what the context
|
|
194
|
+
* window currently holds. Summing turns would count the same cached prefix
|
|
195
|
+
* once per turn.
|
|
196
|
+
*/
|
|
197
|
+
export function summarizeClaudeTokenUsage(
|
|
198
|
+
entries: AnyRecord[],
|
|
199
|
+
configuredContextWindow: string | undefined = process.env.CONTEXT_WINDOW,
|
|
200
|
+
): TokenUsageResult {
|
|
135
201
|
let inputTokens = 0;
|
|
136
202
|
let outputTokens = 0;
|
|
137
203
|
let cacheReadTokens = 0;
|
|
138
204
|
let cacheCreationTokens = 0;
|
|
139
|
-
const lines = fileContent.trim().split('\n');
|
|
140
205
|
|
|
141
|
-
for (let index =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
206
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
207
|
+
const entry = entries[index];
|
|
208
|
+
// A subagent's turns report the subagent's context window, not this
|
|
209
|
+
// conversation's; reading one makes the counter drop to the subagent's
|
|
210
|
+
// number and bounce back on the next main-thread turn.
|
|
211
|
+
if (entry?.isSidechain === true) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
148
214
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
215
|
+
const usage = entry?.type === 'assistant' ? entry.message?.usage : null;
|
|
216
|
+
if (!usage) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const directInputTokens = readUsageNumber(usage.input_tokens ?? usage.inputTokens);
|
|
221
|
+
const rowCacheReadTokens = readUsageNumber(
|
|
222
|
+
usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cacheReadTokens,
|
|
223
|
+
);
|
|
224
|
+
const rowCacheCreationTokens = readUsageNumber(
|
|
225
|
+
usage.cache_creation_input_tokens
|
|
226
|
+
?? usage.cacheCreationInputTokens
|
|
227
|
+
?? usage.cacheCreationTokens,
|
|
228
|
+
);
|
|
229
|
+
const rowInputTokens = directInputTokens + rowCacheReadTokens + rowCacheCreationTokens;
|
|
230
|
+
const rowOutputTokens = readUsageNumber(usage.output_tokens ?? usage.outputTokens);
|
|
231
|
+
|
|
232
|
+
// `<synthetic>` rows — interrupts, API errors, "No response requested" —
|
|
233
|
+
// are written with an all-zero usage block rather than none at all. They
|
|
234
|
+
// never carried a prompt, so treating one as the newest turn zeroed a
|
|
235
|
+
// counter that a live event had just set correctly.
|
|
236
|
+
if (rowInputTokens === 0 && rowOutputTokens === 0) {
|
|
237
|
+
continue;
|
|
163
238
|
}
|
|
239
|
+
|
|
240
|
+
cacheReadTokens = rowCacheReadTokens;
|
|
241
|
+
cacheCreationTokens = rowCacheCreationTokens;
|
|
242
|
+
inputTokens = rowInputTokens;
|
|
243
|
+
outputTokens = rowOutputTokens;
|
|
244
|
+
break;
|
|
164
245
|
}
|
|
165
246
|
|
|
166
247
|
const parsedContextWindow = Number.parseInt(configuredContextWindow ?? '', 10);
|
|
@@ -179,6 +260,22 @@ function readClaudeTokenUsage(fileContent: string, configuredContextWindow: stri
|
|
|
179
260
|
};
|
|
180
261
|
}
|
|
181
262
|
|
|
263
|
+
function parseClaudeUsageEntries(fileContent: string): AnyRecord[] {
|
|
264
|
+
const entries: AnyRecord[] = [];
|
|
265
|
+
for (const line of fileContent.trim().split('\n')) {
|
|
266
|
+
try {
|
|
267
|
+
entries.push(JSON.parse(line) as AnyRecord);
|
|
268
|
+
} catch {
|
|
269
|
+
// Skip malformed lines without discarding usage from earlier messages.
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return entries;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function claudeEntriesHaveUsage(entries: AnyRecord[]): boolean {
|
|
276
|
+
return entries.some((entry) => entry?.type === 'assistant' && entry.message?.usage);
|
|
277
|
+
}
|
|
278
|
+
|
|
182
279
|
function readOpenCodeTokenUsage(databasePath: string, providerSessionId: string): TokenUsageResult {
|
|
183
280
|
const database = new Database(databasePath, { readonly: true, fileMustExist: true });
|
|
184
281
|
try {
|
|
@@ -264,6 +361,24 @@ export function createProviderTokenUsageService(
|
|
|
264
361
|
});
|
|
265
362
|
}
|
|
266
363
|
|
|
364
|
+
// The fallback covers rows whose provider id was never recorded, and for
|
|
365
|
+
// a session discovered from disk the app id *is* its provider id. That
|
|
366
|
+
// stops being true the moment an edit rewinds the conversation off a
|
|
367
|
+
// thread: until the replacement run announces its own id, the fallback
|
|
368
|
+
// would resolve the retired transcript and report the discarded
|
|
369
|
+
// conversation's usage against an empty one.
|
|
370
|
+
if (
|
|
371
|
+
!session.provider_session_id
|
|
372
|
+
&& dependencies.isProviderSessionSuperseded(sessionId, session.provider)
|
|
373
|
+
) {
|
|
374
|
+
return {
|
|
375
|
+
used: 0,
|
|
376
|
+
inputTokens: 0,
|
|
377
|
+
outputTokens: 0,
|
|
378
|
+
breakdown: { input: 0, output: 0 },
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
267
382
|
const providerSessionId = session.provider_session_id || sessionId;
|
|
268
383
|
|
|
269
384
|
if (session.provider === 'cursor') {
|
|
@@ -307,8 +422,16 @@ export function createProviderTokenUsageService(
|
|
|
307
422
|
});
|
|
308
423
|
}
|
|
309
424
|
|
|
310
|
-
const
|
|
311
|
-
|
|
425
|
+
const tail = await dependencies.readTextFileTail(sessionFilePath, TOKEN_USAGE_TAIL_BYTES);
|
|
426
|
+
const tailUsage = findCodexTokenUsage(tail.content);
|
|
427
|
+
if (tailUsage || tail.isComplete) {
|
|
428
|
+
return tailUsage ?? emptyCodexTokenUsage();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// A tail this large with no token_count row is pathological, but the
|
|
432
|
+
// whole file is still authoritative when it happens.
|
|
433
|
+
return findCodexTokenUsage(await dependencies.readTextFile(sessionFilePath))
|
|
434
|
+
?? emptyCodexTokenUsage();
|
|
312
435
|
}
|
|
313
436
|
|
|
314
437
|
let sessionFilePath = session.jsonl_path;
|
|
@@ -345,8 +468,12 @@ export function createProviderTokenUsageService(
|
|
|
345
468
|
});
|
|
346
469
|
}
|
|
347
470
|
|
|
348
|
-
const
|
|
349
|
-
|
|
471
|
+
const tail = await dependencies.readTextFileTail(sessionFilePath, TOKEN_USAGE_TAIL_BYTES);
|
|
472
|
+
let entries = parseClaudeUsageEntries(tail.content);
|
|
473
|
+
if (!claudeEntriesHaveUsage(entries) && !tail.isComplete) {
|
|
474
|
+
entries = parseClaudeUsageEntries(await dependencies.readTextFile(sessionFilePath));
|
|
475
|
+
}
|
|
476
|
+
return summarizeClaudeTokenUsage(entries, dependencies.getClaudeContextWindow());
|
|
350
477
|
},
|
|
351
478
|
};
|
|
352
479
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fsSync
|
|
1
|
+
import fsSync from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
|
|
@@ -38,6 +38,15 @@ type ProjectConversationResult = {
|
|
|
38
38
|
sessions: SessionConversationResult[];
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
type SessionTitleSearchResult = {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
provider: string;
|
|
44
|
+
projectId: string | null;
|
|
45
|
+
projectDisplayName: string;
|
|
46
|
+
sessionTitle: string;
|
|
47
|
+
lastActivity: string | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
41
50
|
export type SessionConversationSearchProgressUpdate = {
|
|
42
51
|
projectResult: ProjectConversationResult | null;
|
|
43
52
|
totalMatches: number;
|
|
@@ -49,6 +58,7 @@ type SearchSessionConversationsInput = {
|
|
|
49
58
|
query: string;
|
|
50
59
|
limit: number;
|
|
51
60
|
signal?: AbortSignal;
|
|
61
|
+
onTitleResults?: (results: SessionTitleSearchResult[]) => void;
|
|
52
62
|
onProgress?: (update: SessionConversationSearchProgressUpdate) => void;
|
|
53
63
|
};
|
|
54
64
|
|
|
@@ -156,6 +166,67 @@ function toSummaryText(customName: string | null, fallback: string | null | unde
|
|
|
156
166
|
return trimmedFallback.length > 50 ? `${trimmedFallback.slice(0, 50)}...` : trimmedFallback;
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Finds visible sessions whose displayed title contains the query. Title
|
|
171
|
+
* matches are resolved from the database before transcript scanning so the UI
|
|
172
|
+
* can always present them first, including sessions without a transcript yet.
|
|
173
|
+
*/
|
|
174
|
+
function findSessionTitleResults(
|
|
175
|
+
sessions: SessionRepositoryRow[],
|
|
176
|
+
query: string,
|
|
177
|
+
limit: number,
|
|
178
|
+
): SessionTitleSearchResult[] {
|
|
179
|
+
const normalizedQuery = query.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
180
|
+
const projectCache = new Map<string, ReturnType<typeof projectsDb.getProjectPath>>();
|
|
181
|
+
|
|
182
|
+
return sessions
|
|
183
|
+
.flatMap((session) => {
|
|
184
|
+
const sessionTitle = toSummaryText(session.custom_name, session.session_id, session.session_id);
|
|
185
|
+
const normalizedTitle = sessionTitle.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
186
|
+
const matchIndex = normalizedTitle.indexOf(normalizedQuery);
|
|
187
|
+
if (matchIndex === -1) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const projectPath = typeof session.project_path === 'string' && session.project_path.trim()
|
|
192
|
+
? session.project_path.trim()
|
|
193
|
+
: null;
|
|
194
|
+
const projectKey = projectPath ?? UNKNOWN_PROJECT_KEY;
|
|
195
|
+
if (!projectCache.has(projectKey)) {
|
|
196
|
+
projectCache.set(
|
|
197
|
+
projectKey,
|
|
198
|
+
projectPath ? projectsDb.getProjectPath(projectPath) : null,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const project = projectCache.get(projectKey) ?? null;
|
|
203
|
+
if (project?.isArchived) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [{
|
|
208
|
+
sessionId: session.session_id,
|
|
209
|
+
provider: session.provider,
|
|
210
|
+
projectId: project?.project_id ?? null,
|
|
211
|
+
projectDisplayName: projectPath
|
|
212
|
+
? (project?.custom_project_name?.trim() || path.basename(projectPath) || projectPath)
|
|
213
|
+
: 'Unknown Project',
|
|
214
|
+
sessionTitle,
|
|
215
|
+
lastActivity: session.updated_at || session.created_at || null,
|
|
216
|
+
matchIndex,
|
|
217
|
+
}];
|
|
218
|
+
})
|
|
219
|
+
.sort((left, right) => {
|
|
220
|
+
if (left.matchIndex !== right.matchIndex) {
|
|
221
|
+
return left.matchIndex - right.matchIndex;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return new Date(right.lastActivity ?? 0).getTime() - new Date(left.lastActivity ?? 0).getTime();
|
|
225
|
+
})
|
|
226
|
+
.slice(0, limit)
|
|
227
|
+
.map(({ matchIndex: _matchIndex, ...result }) => result);
|
|
228
|
+
}
|
|
229
|
+
|
|
159
230
|
function isInternalContent(content: string): boolean {
|
|
160
231
|
return INTERNAL_CONTENT_PREFIXES.some((prefix) => content.startsWith(prefix));
|
|
161
232
|
}
|
|
@@ -1071,28 +1142,42 @@ async function parseSessionMatches(
|
|
|
1071
1142
|
return null;
|
|
1072
1143
|
}
|
|
1073
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Searches session titles and provider transcripts for the provider search
|
|
1147
|
+
* service and its route-level integration tests.
|
|
1148
|
+
*/
|
|
1074
1149
|
export async function searchConversations(
|
|
1075
1150
|
query: string,
|
|
1076
1151
|
limit = 50,
|
|
1077
1152
|
onProjectResult: ((update: SessionConversationSearchProgressUpdate) => void) | null = null,
|
|
1078
1153
|
signal: AbortSignal | null = null,
|
|
1079
|
-
|
|
1154
|
+
onTitleResults: ((results: SessionTitleSearchResult[]) => void) | null = null,
|
|
1155
|
+
): Promise<{
|
|
1156
|
+
results: ProjectConversationResult[];
|
|
1157
|
+
titleResults: SessionTitleSearchResult[];
|
|
1158
|
+
totalMatches: number;
|
|
1159
|
+
query: string;
|
|
1160
|
+
}> {
|
|
1080
1161
|
const safeQuery = typeof query === 'string' ? query.trim() : '';
|
|
1081
1162
|
const safeLimit = Math.max(1, Math.min(Number.isFinite(limit) ? limit : 50, 200));
|
|
1082
1163
|
const words = safeQuery.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
1083
1164
|
|
|
1084
1165
|
if (words.length === 0) {
|
|
1085
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1166
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1086
1167
|
}
|
|
1087
1168
|
|
|
1088
1169
|
const isAborted = () => signal?.aborted === true;
|
|
1089
1170
|
if (isAborted()) {
|
|
1090
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1171
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1091
1172
|
}
|
|
1092
1173
|
|
|
1093
|
-
const
|
|
1174
|
+
const activeSessions = sessionsDb.getAllSessions();
|
|
1175
|
+
const titleResults = findSessionTitleResults(activeSessions, safeQuery, safeLimit);
|
|
1176
|
+
onTitleResults?.(titleResults);
|
|
1177
|
+
|
|
1178
|
+
const searchableSessions = normalizeSearchableSessions(activeSessions);
|
|
1094
1179
|
if (searchableSessions.length === 0) {
|
|
1095
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1180
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1096
1181
|
}
|
|
1097
1182
|
|
|
1098
1183
|
const sessionsByPathKey = new Map<string, SearchableSessionRow[]>();
|
|
@@ -1123,7 +1208,7 @@ export async function searchConversations(
|
|
|
1123
1208
|
signal ?? undefined,
|
|
1124
1209
|
);
|
|
1125
1210
|
if (isAborted() || matchedFileKeys.size === 0) {
|
|
1126
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1211
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1127
1212
|
}
|
|
1128
1213
|
|
|
1129
1214
|
const matchedSessionKeys = new Set<string>();
|
|
@@ -1207,6 +1292,7 @@ export async function searchConversations(
|
|
|
1207
1292
|
|
|
1208
1293
|
return {
|
|
1209
1294
|
results,
|
|
1295
|
+
titleResults,
|
|
1210
1296
|
totalMatches: runtime.totalMatches,
|
|
1211
1297
|
query: safeQuery,
|
|
1212
1298
|
};
|
|
@@ -1229,6 +1315,7 @@ export const sessionConversationsSearchService = {
|
|
|
1229
1315
|
input.limit,
|
|
1230
1316
|
input.onProgress ?? null,
|
|
1231
1317
|
input.signal ?? null,
|
|
1318
|
+
input.onTitleResults ?? null,
|
|
1232
1319
|
);
|
|
1233
1320
|
},
|
|
1234
1321
|
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import type { FetchHistoryResult } from '@/shared/types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Full-transcript cache for session history reads.
|
|
7
|
+
*
|
|
8
|
+
* Every provider history reader materializes the complete normalized
|
|
9
|
+
* transcript and then slices out the requested page, so serving a 20-row page
|
|
10
|
+
* of a large session re-read and re-parsed the whole transcript file on every
|
|
11
|
+
* request — opening a session, each older page while scrolling up, and every
|
|
12
|
+
* post-turn refresh. For a multi-megabyte JSONL that is most of a second of
|
|
13
|
+
* CPU per request.
|
|
14
|
+
*
|
|
15
|
+
* Entries are keyed by app session id and validated with one `stat` per
|
|
16
|
+
* request against the transcript file's identity (path + mtime + size), so
|
|
17
|
+
* only the first read after the file changes pays the parse. Anything that
|
|
18
|
+
* rewrites history (a new turn, an edit, a rewind, a fork) touches the file
|
|
19
|
+
* and invalidates naturally; no explicit invalidation hooks exist or are
|
|
20
|
+
* needed.
|
|
21
|
+
*
|
|
22
|
+
* Only history readers that read `jsonl_path` itself may use this cache —
|
|
23
|
+
* callers pass `transcriptPath: null` for providers whose messages live
|
|
24
|
+
* elsewhere (Cursor's store.db, OpenCode's shared SQLite), which bypasses
|
|
25
|
+
* caching entirely.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type CacheEntry = {
|
|
29
|
+
transcriptPath: string;
|
|
30
|
+
mtimeMs: number;
|
|
31
|
+
/** File size in bytes; doubles as the entry's cost against the byte budget. */
|
|
32
|
+
size: number;
|
|
33
|
+
full: FetchHistoryResult;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type GetFullHistoryArgs = {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** Path of the file the provider's history reader actually parses, or null to bypass. */
|
|
39
|
+
transcriptPath: string | null | undefined;
|
|
40
|
+
/** Loads the complete transcript (`limit: null, offset: 0`) from the provider. */
|
|
41
|
+
loadFull: () => Promise<FetchHistoryResult>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A transcript entry's heap cost is roughly the file it was parsed from, so
|
|
46
|
+
* the budget is expressed in file bytes. The newest entry is always retained
|
|
47
|
+
* even when it alone exceeds the budget — evicting it would just re-parse the
|
|
48
|
+
* same file on the next request.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_CACHED_TRANSCRIPT_FILE_BYTES = 256 * 1024 * 1024;
|
|
51
|
+
const MAX_CACHE_ENTRIES = 8;
|
|
52
|
+
|
|
53
|
+
export function createSessionHistoryCache(
|
|
54
|
+
maxTotalFileBytes = MAX_CACHED_TRANSCRIPT_FILE_BYTES,
|
|
55
|
+
maxEntries = MAX_CACHE_ENTRIES,
|
|
56
|
+
) {
|
|
57
|
+
const entries = new Map<string, CacheEntry>();
|
|
58
|
+
const pendingLoads = new Map<string, Promise<FetchHistoryResult>>();
|
|
59
|
+
|
|
60
|
+
function evictOverBudget(): void {
|
|
61
|
+
let totalBytes = 0;
|
|
62
|
+
for (const entry of entries.values()) {
|
|
63
|
+
totalBytes += entry.size;
|
|
64
|
+
}
|
|
65
|
+
for (const key of entries.keys()) {
|
|
66
|
+
if (entries.size <= 1 || (totalBytes <= maxTotalFileBytes && entries.size <= maxEntries)) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
totalBytes -= entries.get(key)!.size;
|
|
70
|
+
entries.delete(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
/**
|
|
76
|
+
* Returns the session's full transcript through the cache, or null when
|
|
77
|
+
* the session is not cacheable (no transcript path, or the file cannot be
|
|
78
|
+
* stat'ed) — the caller then falls back to a plain provider read.
|
|
79
|
+
*/
|
|
80
|
+
async getFullHistory({ sessionId, transcriptPath, loadFull }: GetFullHistoryArgs): Promise<FetchHistoryResult | null> {
|
|
81
|
+
if (!transcriptPath) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let stat;
|
|
86
|
+
try {
|
|
87
|
+
stat = await fsp.stat(transcriptPath);
|
|
88
|
+
} catch {
|
|
89
|
+
entries.delete(sessionId);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (!stat.isFile()) {
|
|
93
|
+
entries.delete(sessionId);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const cached = entries.get(sessionId);
|
|
98
|
+
if (
|
|
99
|
+
cached
|
|
100
|
+
&& cached.transcriptPath === transcriptPath
|
|
101
|
+
&& cached.mtimeMs === stat.mtimeMs
|
|
102
|
+
&& cached.size === stat.size
|
|
103
|
+
) {
|
|
104
|
+
// Re-insert to mark as most recently used.
|
|
105
|
+
entries.delete(sessionId);
|
|
106
|
+
entries.set(sessionId, cached);
|
|
107
|
+
return cached.full;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Concurrent requests for the same session share one parse. The file may
|
|
111
|
+
// gain rows while the load runs; the pre-load stat is what the entry is
|
|
112
|
+
// keyed by, so the next request would see a changed stat and re-read.
|
|
113
|
+
const pending = pendingLoads.get(sessionId);
|
|
114
|
+
if (pending) {
|
|
115
|
+
return pending;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const load = loadFull().then((full) => {
|
|
119
|
+
entries.delete(sessionId);
|
|
120
|
+
entries.set(sessionId, {
|
|
121
|
+
transcriptPath,
|
|
122
|
+
mtimeMs: stat.mtimeMs,
|
|
123
|
+
size: stat.size,
|
|
124
|
+
full,
|
|
125
|
+
});
|
|
126
|
+
evictOverBudget();
|
|
127
|
+
return full;
|
|
128
|
+
});
|
|
129
|
+
pendingLoads.set(sessionId, load);
|
|
130
|
+
try {
|
|
131
|
+
return await load;
|
|
132
|
+
} finally {
|
|
133
|
+
pendingLoads.delete(sessionId);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const sessionHistoryCache = createSessionHistoryCache();
|