@cloudcli-ai/cloudcli 1.34.0 → 1.35.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/README.de.md +9 -1
- package/README.ja.md +8 -0
- package/README.ko.md +9 -1
- package/README.md +16 -3
- package/README.ru.md +9 -1
- package/README.tr.md +8 -1
- package/README.zh-CN.md +9 -1
- package/README.zh-TW.md +9 -1
- package/dist/api-docs.html +36 -18
- package/dist/assets/index-CtwxqzGG.js +1407 -0
- package/dist/assets/index-DlvtxFyt.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/browser-use-mcp.js +350 -0
- package/dist-server/server/browser-use-mcp.js.map +1 -0
- package/dist-server/server/claude-sdk.js +25 -5
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +14 -6
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/computer-use-agent.js +242 -0
- package/dist-server/server/computer-use-agent.js.map +1 -0
- package/dist-server/server/computer-use-mcp.js +519 -0
- package/dist-server/server/computer-use-mcp.js.map +1 -0
- package/dist-server/server/cursor-cli.js +27 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +17 -2
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +149 -40
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +1 -1
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +55 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js +2 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-executor.js +212 -0
- package/dist-server/server/modules/computer-use/computer-executor.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js +422 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +126 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js +196 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js +759 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +126 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/index.js +3 -0
- package/dist-server/server/modules/computer-use/index.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +59 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +106 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +78 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +68 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +16 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +1 -0
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +23 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +26 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +4 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
- 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 +7 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
- 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 +3 -9
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +123 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +1 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +24 -14
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +21 -8
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +19 -1
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/settings.js +1 -1
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +1 -176
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
- package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +73 -1
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/voice-proxy.js +226 -0
- package/dist-server/server/voice-proxy.js.map +1 -0
- package/electron/assets/logo-macos.icns +0 -0
- package/electron/assets/logo-macos.png +0 -0
- package/electron/assets/logo-windows.ico +0 -0
- package/electron/cloud.js +260 -0
- package/electron/computerAgent.js +290 -0
- package/electron/desktopNotifications.js +378 -0
- package/electron/desktopWindow.js +781 -0
- package/electron/launcher/index.html +14 -0
- package/electron/launcher/launcher.css +801 -0
- package/electron/launcher/launcher.js +829 -0
- package/electron/localServer.js +550 -0
- package/electron/main.js +1060 -0
- package/electron/preload.cjs +63 -0
- package/electron/scripts/generate-macos-icon.js +62 -0
- package/electron/serverInstaller.js +277 -0
- package/electron/tabs.js +87 -0
- package/electron/viewHost.js +331 -0
- package/package.json +86 -5
- package/public/api-docs.html +36 -18
- package/scripts/build-computer-semantics.mjs +133 -0
- package/scripts/copy-computer-semantics-bin.mjs +24 -0
- package/scripts/release/build-server-bundle.js +176 -0
- package/scripts/release/prepare-desktop-app.js +158 -0
- package/server/browser-use-mcp.ts +384 -0
- package/server/claude-sdk.js +27 -5
- package/server/cli.js +15 -6
- package/server/computer-use-agent.ts +279 -0
- package/server/computer-use-mcp.ts +574 -0
- package/server/cursor-cli.js +27 -11
- package/server/gemini-cli.js +17 -2
- package/server/index.js +151 -47
- package/server/load-env.js +1 -1
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +836 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
- package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
- package/server/modules/computer-use/computer-executor.ts +242 -0
- package/server/modules/computer-use/computer-semantics.service.ts +460 -0
- package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
- package/server/modules/computer-use/computer-use.routes.ts +211 -0
- package/server/modules/computer-use/computer-use.service.ts +920 -0
- package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
- package/server/modules/computer-use/index.ts +2 -0
- package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
- package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
- package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
- package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
- package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
- package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
- package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
- package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +25 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +12 -1
- package/server/modules/database/repositories/sessions.db.ts +227 -23
- package/server/modules/database/schema.ts +27 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
- package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
- package/server/modules/notifications/index.ts +13 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/projects/services/project-management.service.ts +0 -8
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
- package/server/modules/providers/README.md +1 -1
- package/server/modules/providers/index.ts +1 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
- package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
- package/server/modules/providers/provider.routes.ts +167 -1
- package/server/modules/providers/services/mcp.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
- package/server/modules/providers/services/sessions.service.ts +79 -4
- package/server/modules/providers/services/skills.service.ts +25 -1
- package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
- package/server/modules/providers/tests/skills.test.ts +212 -0
- package/server/modules/websocket/README.md +33 -27
- package/server/modules/websocket/index.ts +1 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
- package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
- package/server/modules/websocket/services/websocket-server.service.ts +12 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
- package/server/openai-codex.js +24 -14
- package/server/opencode-cli.js +21 -8
- package/server/opencode-cli.test.js +20 -1
- package/server/routes/settings.js +6 -1
- package/server/services/notification-orchestrator.js +3 -224
- package/server/services/tests/notification-orchestrator.test.js +80 -0
- package/server/shared/interfaces.ts +15 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +84 -1
- package/server/shared/utils.ts +95 -1
- package/server/voice-proxy.js +224 -0
- package/dist/assets/index-73Xw3i9v.css +0 -32
- package/dist/assets/index-BIHaviaA.js +0 -1361
- package/dist/modelConstants.js +0 -854
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
- package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
- package/public/modelConstants.js +0 -854
- /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
- /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
- /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
|
@@ -4,6 +4,7 @@ export { apiKeysDb } from '@/modules/database/repositories/api-keys.js';
|
|
|
4
4
|
export { appConfigDb } from '@/modules/database/repositories/app-config.js';
|
|
5
5
|
export { credentialsDb } from '@/modules/database/repositories/credentials.js';
|
|
6
6
|
export { githubTokensDb } from '@/modules/database/repositories/github-tokens.js';
|
|
7
|
+
export { notificationChannelEndpointsDb } from '@/modules/database/repositories/notification-channel-endpoints.js';
|
|
7
8
|
export { notificationPreferencesDb } from '@/modules/database/repositories/notification-preferences.js';
|
|
8
9
|
export { projectsDb } from '@/modules/database/repositories/projects.db.js';
|
|
9
10
|
export { pushSubscriptionsDb } from '@/modules/database/repositories/push-subscriptions.js';
|
|
@@ -3,6 +3,7 @@ import { Database } from 'better-sqlite3';
|
|
|
3
3
|
import {
|
|
4
4
|
APP_CONFIG_TABLE_SCHEMA_SQL,
|
|
5
5
|
LAST_SCANNED_AT_SQL,
|
|
6
|
+
NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL,
|
|
6
7
|
PROJECTS_TABLE_SCHEMA_SQL,
|
|
7
8
|
PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL,
|
|
8
9
|
SESSIONS_TABLE_SCHEMA_SQL,
|
|
@@ -382,6 +383,25 @@ const rebuildSessionsTableWithProjectSchema = (db: Database): void => {
|
|
|
382
383
|
}
|
|
383
384
|
};
|
|
384
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Adds the `provider_session_id` mapping column used by the session gateway.
|
|
388
|
+
*
|
|
389
|
+
* Rows that existed before this migration were always keyed directly by the
|
|
390
|
+
* provider-native session id, so backfilling `provider_session_id` with
|
|
391
|
+
* `session_id` keeps every legacy row resolvable through the new mapping.
|
|
392
|
+
*/
|
|
393
|
+
const addProviderSessionIdMapping = (db: Database): void => {
|
|
394
|
+
const sessionsTableInfo = getTableInfo(db, 'sessions');
|
|
395
|
+
const columnNames = sessionsTableInfo.map((column) => column.name);
|
|
396
|
+
|
|
397
|
+
addColumnToTableIfNotExists(db, 'sessions', columnNames, 'provider_session_id', 'TEXT');
|
|
398
|
+
db.exec(`
|
|
399
|
+
UPDATE sessions
|
|
400
|
+
SET provider_session_id = session_id
|
|
401
|
+
WHERE provider_session_id IS NULL
|
|
402
|
+
`);
|
|
403
|
+
};
|
|
404
|
+
|
|
385
405
|
const ensureProjectsForSessionPaths = (db: Database): void => {
|
|
386
406
|
if (!tableExists(db, 'sessions')) {
|
|
387
407
|
return;
|
|
@@ -421,6 +441,9 @@ export const runMigrations = (db: Database) => {
|
|
|
421
441
|
db.exec(VAPID_KEYS_TABLE_SCHEMA_SQL);
|
|
422
442
|
db.exec(PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL);
|
|
423
443
|
db.exec('CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id)');
|
|
444
|
+
db.exec(NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL);
|
|
445
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_user_channel ON notification_channel_endpoints(user_id, channel)');
|
|
446
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_enabled ON notification_channel_endpoints(enabled)');
|
|
424
447
|
|
|
425
448
|
db.exec(PROJECTS_TABLE_SCHEMA_SQL);
|
|
426
449
|
rebuildProjectsTableWithPrimaryKeySchema(db);
|
|
@@ -428,9 +451,11 @@ export const runMigrations = (db: Database) => {
|
|
|
428
451
|
migrateLegacyWorkspaceTableIntoProjects(db);
|
|
429
452
|
rebuildSessionsTableWithProjectSchema(db);
|
|
430
453
|
migrateLegacySessionNames(db);
|
|
454
|
+
addProviderSessionIdMapping(db);
|
|
431
455
|
ensureProjectsForSessionPaths(db);
|
|
432
456
|
|
|
433
457
|
db.exec('CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id)');
|
|
458
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_provider_session_id ON sessions(provider_session_id)');
|
|
434
459
|
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_project_path ON sessions(project_path)');
|
|
435
460
|
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_is_archived ON sessions(isArchived)');
|
|
436
461
|
db.exec('CREATE INDEX IF NOT EXISTS idx_projects_is_starred ON projects(isStarred)');
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { getConnection } from '@/modules/database/connection.js';
|
|
2
|
+
|
|
3
|
+
type NotificationChannelEndpointRow = {
|
|
4
|
+
id: number;
|
|
5
|
+
user_id: number;
|
|
6
|
+
channel: string;
|
|
7
|
+
endpoint_id: string;
|
|
8
|
+
label: string | null;
|
|
9
|
+
metadata_json: string | null;
|
|
10
|
+
enabled: number;
|
|
11
|
+
last_seen_at: string;
|
|
12
|
+
created_at: string;
|
|
13
|
+
updated_at: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type UpsertNotificationChannelEndpointInput = {
|
|
17
|
+
userId: number;
|
|
18
|
+
channel: string;
|
|
19
|
+
endpointId: string;
|
|
20
|
+
label?: string | null;
|
|
21
|
+
metadata?: Record<string, unknown> | null;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function normalizeRequiredText(value: unknown): string {
|
|
26
|
+
if (typeof value !== 'string') return '';
|
|
27
|
+
return value.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeNullableText(value: unknown): string | null {
|
|
31
|
+
if (typeof value !== 'string') return null;
|
|
32
|
+
const normalized = value.trim();
|
|
33
|
+
return normalized || null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function serializeMetadata(metadata: Record<string, unknown> | null | undefined): string | null {
|
|
37
|
+
if (!metadata || typeof metadata !== 'object') return null;
|
|
38
|
+
return JSON.stringify(metadata);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseMetadata(metadataJson: string | null): Record<string, unknown> {
|
|
42
|
+
if (!metadataJson) return {};
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(metadataJson);
|
|
45
|
+
return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : {};
|
|
46
|
+
} catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const notificationChannelEndpointsDb = {
|
|
52
|
+
upsertEndpoint(input: UpsertNotificationChannelEndpointInput): NotificationChannelEndpointRow {
|
|
53
|
+
const channel = normalizeRequiredText(input.channel);
|
|
54
|
+
const endpointId = normalizeRequiredText(input.endpointId);
|
|
55
|
+
if (!channel) throw new Error('channel is required');
|
|
56
|
+
if (!endpointId) throw new Error('endpointId is required');
|
|
57
|
+
|
|
58
|
+
const enabled = input.enabled === false ? 0 : 1;
|
|
59
|
+
const db = getConnection();
|
|
60
|
+
db.prepare(
|
|
61
|
+
`INSERT INTO notification_channel_endpoints (
|
|
62
|
+
user_id,
|
|
63
|
+
channel,
|
|
64
|
+
endpoint_id,
|
|
65
|
+
label,
|
|
66
|
+
metadata_json,
|
|
67
|
+
enabled,
|
|
68
|
+
last_seen_at,
|
|
69
|
+
updated_at
|
|
70
|
+
)
|
|
71
|
+
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
72
|
+
ON CONFLICT(user_id, channel, endpoint_id) DO UPDATE SET
|
|
73
|
+
label = excluded.label,
|
|
74
|
+
metadata_json = excluded.metadata_json,
|
|
75
|
+
enabled = excluded.enabled,
|
|
76
|
+
last_seen_at = CURRENT_TIMESTAMP,
|
|
77
|
+
updated_at = CURRENT_TIMESTAMP`
|
|
78
|
+
).run(
|
|
79
|
+
input.userId,
|
|
80
|
+
channel,
|
|
81
|
+
endpointId,
|
|
82
|
+
normalizeNullableText(input.label),
|
|
83
|
+
serializeMetadata(input.metadata),
|
|
84
|
+
enabled
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return notificationChannelEndpointsDb.getEndpoint(input.userId, channel, endpointId)!;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
getEndpoint(userId: number, channel: string, endpointId: string): NotificationChannelEndpointRow | null {
|
|
91
|
+
const db = getConnection();
|
|
92
|
+
const row = db.prepare(
|
|
93
|
+
`SELECT id, user_id, channel, endpoint_id, label, metadata_json, enabled, last_seen_at, created_at, updated_at
|
|
94
|
+
FROM notification_channel_endpoints
|
|
95
|
+
WHERE user_id = ? AND channel = ? AND endpoint_id = ?`
|
|
96
|
+
).get(
|
|
97
|
+
userId,
|
|
98
|
+
normalizeRequiredText(channel),
|
|
99
|
+
normalizeRequiredText(endpointId)
|
|
100
|
+
) as NotificationChannelEndpointRow | undefined;
|
|
101
|
+
return row || null;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getEndpoints(userId: number, channel: string): NotificationChannelEndpointRow[] {
|
|
105
|
+
const db = getConnection();
|
|
106
|
+
return db.prepare(
|
|
107
|
+
`SELECT id, user_id, channel, endpoint_id, label, metadata_json, enabled, last_seen_at, created_at, updated_at
|
|
108
|
+
FROM notification_channel_endpoints
|
|
109
|
+
WHERE user_id = ? AND channel = ?
|
|
110
|
+
ORDER BY last_seen_at DESC`
|
|
111
|
+
).all(userId, normalizeRequiredText(channel)) as NotificationChannelEndpointRow[];
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
getEnabledEndpoints(userId: number, channel: string): NotificationChannelEndpointRow[] {
|
|
115
|
+
const db = getConnection();
|
|
116
|
+
return db.prepare(
|
|
117
|
+
`SELECT id, user_id, channel, endpoint_id, label, metadata_json, enabled, last_seen_at, created_at, updated_at
|
|
118
|
+
FROM notification_channel_endpoints
|
|
119
|
+
WHERE user_id = ? AND channel = ? AND enabled = 1
|
|
120
|
+
ORDER BY last_seen_at DESC`
|
|
121
|
+
).all(userId, normalizeRequiredText(channel)) as NotificationChannelEndpointRow[];
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
setEndpointEnabled(userId: number, channel: string, endpointId: string, enabled: boolean): boolean {
|
|
125
|
+
const db = getConnection();
|
|
126
|
+
const result = db.prepare(
|
|
127
|
+
`UPDATE notification_channel_endpoints
|
|
128
|
+
SET enabled = ?, updated_at = CURRENT_TIMESTAMP
|
|
129
|
+
WHERE user_id = ? AND channel = ? AND endpoint_id = ?`
|
|
130
|
+
).run(enabled ? 1 : 0, userId, normalizeRequiredText(channel), normalizeRequiredText(endpointId));
|
|
131
|
+
return result.changes > 0;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
touchEndpoint(userId: number, channel: string, endpointId: string): boolean {
|
|
135
|
+
const db = getConnection();
|
|
136
|
+
const result = db.prepare(
|
|
137
|
+
`UPDATE notification_channel_endpoints
|
|
138
|
+
SET last_seen_at = CURRENT_TIMESTAMP
|
|
139
|
+
WHERE user_id = ? AND channel = ? AND endpoint_id = ?`
|
|
140
|
+
).run(userId, normalizeRequiredText(channel), normalizeRequiredText(endpointId));
|
|
141
|
+
return result.changes > 0;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
removeEndpoint(userId: number, channel: string, endpointId: string): boolean {
|
|
145
|
+
const db = getConnection();
|
|
146
|
+
const result = db.prepare(
|
|
147
|
+
'DELETE FROM notification_channel_endpoints WHERE user_id = ? AND channel = ? AND endpoint_id = ?'
|
|
148
|
+
).run(userId, normalizeRequiredText(channel), normalizeRequiredText(endpointId));
|
|
149
|
+
return result.changes > 0;
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
parseMetadata,
|
|
153
|
+
};
|
|
@@ -10,7 +10,9 @@ type NotificationPreferences = {
|
|
|
10
10
|
channels: {
|
|
11
11
|
inApp: boolean;
|
|
12
12
|
webPush: boolean;
|
|
13
|
+
desktop: boolean;
|
|
13
14
|
sound: boolean;
|
|
15
|
+
[key: string]: boolean;
|
|
14
16
|
};
|
|
15
17
|
events: {
|
|
16
18
|
actionRequired: boolean;
|
|
@@ -23,6 +25,7 @@ const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences = {
|
|
|
23
25
|
channels: {
|
|
24
26
|
inApp: false,
|
|
25
27
|
webPush: false,
|
|
28
|
+
desktop: false,
|
|
26
29
|
sound: true,
|
|
27
30
|
},
|
|
28
31
|
events: {
|
|
@@ -34,11 +37,20 @@ const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences = {
|
|
|
34
37
|
|
|
35
38
|
function normalizeNotificationPreferences(value: unknown): NotificationPreferences {
|
|
36
39
|
const source = value && typeof value === 'object' ? (value as Record<string, any>) : {};
|
|
40
|
+
const sourceChannels = source.channels && typeof source.channels === 'object'
|
|
41
|
+
? source.channels as Record<string, unknown>
|
|
42
|
+
: {};
|
|
43
|
+
const extraChannels = Object.fromEntries(
|
|
44
|
+
Object.entries(sourceChannels)
|
|
45
|
+
.filter(([key, channelValue]) => !['inApp', 'webPush', 'desktop', 'sound'].includes(key) && typeof channelValue === 'boolean')
|
|
46
|
+
) as Record<string, boolean>;
|
|
37
47
|
|
|
38
48
|
return {
|
|
39
49
|
channels: {
|
|
50
|
+
...extraChannels,
|
|
40
51
|
inApp: source.channels?.inApp === true,
|
|
41
52
|
webPush: source.channels?.webPush === true,
|
|
53
|
+
desktop: source.channels?.desktop === true,
|
|
42
54
|
sound: source.channels?.sound !== false,
|
|
43
55
|
},
|
|
44
56
|
events: {
|
|
@@ -103,4 +115,3 @@ export const notificationPreferencesDb = {
|
|
|
103
115
|
return notificationPreferencesDb.updateNotificationPreferences(userId, preferences);
|
|
104
116
|
},
|
|
105
117
|
};
|
|
106
|
-
|
|
@@ -5,6 +5,7 @@ import { normalizeProjectPath } from '@/shared/utils.js';
|
|
|
5
5
|
type SessionRow = {
|
|
6
6
|
session_id: string;
|
|
7
7
|
provider: string;
|
|
8
|
+
provider_session_id: string | null;
|
|
8
9
|
project_path: string | null;
|
|
9
10
|
jsonl_path: string | null;
|
|
10
11
|
custom_name: string | null;
|
|
@@ -13,15 +14,22 @@ type SessionRow = {
|
|
|
13
14
|
updated_at: string;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const SESSION_ROW_COLUMNS =
|
|
18
|
+
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at';
|
|
19
|
+
|
|
20
|
+
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
20
21
|
|
|
21
22
|
function normalizeTimestamp(value?: string): string | null {
|
|
22
23
|
if (!value) return null;
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// SQLite CURRENT_TIMESTAMP is stored as UTC without a timezone suffix.
|
|
26
|
+
// Normalize it here so every session reader returns canonical ISO strings
|
|
27
|
+
// and the sidebar never interprets fresh rows as local-time "hours old".
|
|
28
|
+
const normalizedValue = SQLITE_UTC_TIMESTAMP_REGEX.test(value)
|
|
29
|
+
? `${value.replace(' ', 'T')}Z`
|
|
30
|
+
: value;
|
|
31
|
+
|
|
32
|
+
const parsed = new Date(normalizedValue);
|
|
25
33
|
if (Number.isNaN(parsed.getTime())) {
|
|
26
34
|
return null;
|
|
27
35
|
}
|
|
@@ -29,14 +37,38 @@ function normalizeTimestamp(value?: string): string | null {
|
|
|
29
37
|
return parsed.toISOString();
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
function normalizeSessionRow<T extends SessionRow | null | undefined>(row: T): T {
|
|
41
|
+
if (!row) {
|
|
42
|
+
return row;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...row,
|
|
47
|
+
created_at: normalizeTimestamp(row.created_at) ?? row.created_at,
|
|
48
|
+
updated_at: normalizeTimestamp(row.updated_at) ?? row.updated_at,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeSessionRows(rows: SessionRow[]): SessionRow[] {
|
|
53
|
+
return rows.map((row) => normalizeSessionRow(row) as SessionRow);
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
|
33
57
|
void provider;
|
|
34
58
|
return normalizeProjectPath(projectPath);
|
|
35
59
|
}
|
|
36
60
|
|
|
37
61
|
export const sessionsDb = {
|
|
62
|
+
/**
|
|
63
|
+
* Upserts one session row discovered on disk by a provider synchronizer.
|
|
64
|
+
*
|
|
65
|
+
* The given id is the provider-native session id. Rows are keyed by
|
|
66
|
+
* `provider_session_id` so a session that was first created by the app
|
|
67
|
+
* (with an app-allocated `session_id`) is updated in place once its
|
|
68
|
+
* transcript shows up on disk, instead of producing a duplicate row.
|
|
69
|
+
*/
|
|
38
70
|
createSession(
|
|
39
|
-
|
|
71
|
+
providerSessionId: string,
|
|
40
72
|
provider: string,
|
|
41
73
|
projectPath: string,
|
|
42
74
|
customName?: string,
|
|
@@ -53,19 +85,54 @@ export const sessionsDb = {
|
|
|
53
85
|
// since it's a foreign key in the sessions table.
|
|
54
86
|
projectsDb.createProjectPath(normalizedProjectPath);
|
|
55
87
|
|
|
88
|
+
const existing = db
|
|
89
|
+
.prepare(
|
|
90
|
+
`SELECT session_id FROM sessions
|
|
91
|
+
WHERE provider_session_id = ? AND provider = ?
|
|
92
|
+
LIMIT 1`
|
|
93
|
+
)
|
|
94
|
+
.get(providerSessionId, provider) as { session_id: string } | undefined;
|
|
95
|
+
|
|
96
|
+
if (existing) {
|
|
97
|
+
db.prepare(
|
|
98
|
+
`UPDATE sessions SET
|
|
99
|
+
provider = ?,
|
|
100
|
+
updated_at = COALESCE(?, CURRENT_TIMESTAMP),
|
|
101
|
+
project_path = ?,
|
|
102
|
+
jsonl_path = ?,
|
|
103
|
+
isArchived = 0,
|
|
104
|
+
custom_name = COALESCE(?, custom_name)
|
|
105
|
+
WHERE session_id = ?`
|
|
106
|
+
).run(
|
|
107
|
+
provider,
|
|
108
|
+
updatedAtValue,
|
|
109
|
+
normalizedProjectPath,
|
|
110
|
+
jsonlPath ?? null,
|
|
111
|
+
customName ?? null,
|
|
112
|
+
existing.session_id
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return existing.session_id;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sessions created outside the app (directly via the provider CLI) are
|
|
119
|
+
// keyed by the provider-native id for both columns. The ON CONFLICT path
|
|
120
|
+
// covers legacy rows that predate the provider_session_id mapping.
|
|
56
121
|
db.prepare(
|
|
57
|
-
`INSERT INTO sessions (session_id, provider, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
58
|
-
VALUES (?, ?, ?, ?, ?, 0, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
|
122
|
+
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
|
59
124
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
60
125
|
provider = excluded.provider,
|
|
126
|
+
provider_session_id = excluded.provider_session_id,
|
|
61
127
|
updated_at = excluded.updated_at,
|
|
62
128
|
project_path = excluded.project_path,
|
|
63
129
|
jsonl_path = excluded.jsonl_path,
|
|
64
130
|
isArchived = 0,
|
|
65
131
|
custom_name = COALESCE(excluded.custom_name, sessions.custom_name)`
|
|
66
132
|
).run(
|
|
67
|
-
|
|
133
|
+
providerSessionId,
|
|
68
134
|
provider,
|
|
135
|
+
providerSessionId,
|
|
69
136
|
customName ?? null,
|
|
70
137
|
normalizedProjectPath,
|
|
71
138
|
jsonlPath ?? null,
|
|
@@ -73,9 +140,77 @@ export const sessionsDb = {
|
|
|
73
140
|
updatedAtValue
|
|
74
141
|
);
|
|
75
142
|
|
|
143
|
+
return providerSessionId;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Inserts one app-allocated session row before any provider run happens.
|
|
148
|
+
*
|
|
149
|
+
* The session gateway uses this when the frontend starts a brand-new chat:
|
|
150
|
+
* `session_id` is the stable app-facing id, while `provider_session_id`
|
|
151
|
+
* stays NULL until the provider runtime announces its own id and
|
|
152
|
+
* `assignProviderSessionId` records the mapping.
|
|
153
|
+
*/
|
|
154
|
+
createAppSession(sessionId: string, provider: string, projectPath: string): string {
|
|
155
|
+
const db = getConnection();
|
|
156
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
157
|
+
|
|
158
|
+
projectsDb.createProjectPath(normalizedProjectPath);
|
|
159
|
+
|
|
160
|
+
db.prepare(
|
|
161
|
+
`INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
|
162
|
+
VALUES (?, ?, NULL, NULL, ?, NULL, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
|
|
163
|
+
).run(sessionId, provider, normalizedProjectPath);
|
|
164
|
+
|
|
76
165
|
return sessionId;
|
|
77
166
|
},
|
|
78
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Records the provider-native session id for one app-allocated session.
|
|
170
|
+
*
|
|
171
|
+
* If the filesystem watcher indexed the provider transcript before this
|
|
172
|
+
* mapping was recorded (a duplicate row keyed by the provider id exists),
|
|
173
|
+
* the duplicate is merged into the app row: its transcript path and name
|
|
174
|
+
* are adopted and the duplicate row is removed. Runs in a transaction so
|
|
175
|
+
* the sidebar can never observe both rows at once.
|
|
176
|
+
*/
|
|
177
|
+
assignProviderSessionId(sessionId: string, providerSessionId: string): void {
|
|
178
|
+
const db = getConnection();
|
|
179
|
+
|
|
180
|
+
const merge = db.transaction(() => {
|
|
181
|
+
const duplicate = db
|
|
182
|
+
.prepare(
|
|
183
|
+
`SELECT ${SESSION_ROW_COLUMNS} FROM sessions
|
|
184
|
+
WHERE (session_id = ? OR provider_session_id = ?)
|
|
185
|
+
AND session_id <> ?
|
|
186
|
+
LIMIT 1`
|
|
187
|
+
)
|
|
188
|
+
.get(providerSessionId, providerSessionId, sessionId) as SessionRow | undefined;
|
|
189
|
+
|
|
190
|
+
if (duplicate) {
|
|
191
|
+
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(duplicate.session_id);
|
|
192
|
+
db.prepare(
|
|
193
|
+
`UPDATE sessions SET
|
|
194
|
+
provider_session_id = ?,
|
|
195
|
+
jsonl_path = COALESCE(jsonl_path, ?),
|
|
196
|
+
custom_name = COALESCE(custom_name, ?),
|
|
197
|
+
updated_at = CURRENT_TIMESTAMP
|
|
198
|
+
WHERE session_id = ?`
|
|
199
|
+
).run(providerSessionId, duplicate.jsonl_path, duplicate.custom_name, sessionId);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
db.prepare(
|
|
204
|
+
`UPDATE sessions SET
|
|
205
|
+
provider_session_id = ?,
|
|
206
|
+
updated_at = CURRENT_TIMESTAMP
|
|
207
|
+
WHERE session_id = ?`
|
|
208
|
+
).run(providerSessionId, sessionId);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
merge();
|
|
212
|
+
},
|
|
213
|
+
|
|
79
214
|
updateSessionCustomName(sessionId: string, customName: string): void {
|
|
80
215
|
const db = getConnection();
|
|
81
216
|
db.prepare(
|
|
@@ -85,30 +220,91 @@ export const sessionsDb = {
|
|
|
85
220
|
).run(customName, sessionId);
|
|
86
221
|
},
|
|
87
222
|
|
|
88
|
-
getSessionById(sessionId: string):
|
|
223
|
+
getSessionById(sessionId: string): SessionRow | null {
|
|
89
224
|
const db = getConnection();
|
|
90
225
|
const row = db
|
|
91
226
|
.prepare(
|
|
92
|
-
`SELECT
|
|
227
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
93
228
|
FROM sessions
|
|
94
229
|
WHERE session_id = ?
|
|
95
230
|
ORDER BY updated_at DESC
|
|
96
231
|
LIMIT 1`
|
|
97
232
|
)
|
|
98
|
-
.get(sessionId) as
|
|
233
|
+
.get(sessionId) as SessionRow | undefined;
|
|
99
234
|
|
|
100
|
-
return row ?? null;
|
|
235
|
+
return normalizeSessionRow(row) ?? null;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Resolves one session row through the provider-native id.
|
|
240
|
+
*
|
|
241
|
+
* The filesystem watcher only knows provider ids (they come from transcript
|
|
242
|
+
* file names), so it uses this lookup to translate disk artifacts back to
|
|
243
|
+
* the app-facing session row before broadcasting sidebar updates.
|
|
244
|
+
*/
|
|
245
|
+
getSessionByProviderSessionId(providerSessionId: string): SessionRow | null {
|
|
246
|
+
const db = getConnection();
|
|
247
|
+
const row = db
|
|
248
|
+
.prepare(
|
|
249
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
250
|
+
FROM sessions
|
|
251
|
+
WHERE provider_session_id = ?
|
|
252
|
+
ORDER BY updated_at DESC
|
|
253
|
+
LIMIT 1`
|
|
254
|
+
)
|
|
255
|
+
.get(providerSessionId) as SessionRow | undefined;
|
|
256
|
+
|
|
257
|
+
return normalizeSessionRow(row) ?? null;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Finds the newest app-created session for a project that is still waiting
|
|
262
|
+
* for its provider-native id to be recorded.
|
|
263
|
+
*
|
|
264
|
+
* Primary intention: OpenCode can expose a new session in its shared
|
|
265
|
+
* `opencode.db` before the websocket runtime reports that same provider id
|
|
266
|
+
* back to our app. At that moment the sidebar already has an optimistic
|
|
267
|
+
* app-owned session row, but the watcher only knows the provider-native id.
|
|
268
|
+
*
|
|
269
|
+
* Without this lookup, the synchronizer would insert a second row keyed by
|
|
270
|
+
* the provider id, then `assignProviderSessionId()` would merge it a moment
|
|
271
|
+
* later. That eventually self-heals, but on slow networks the user can still
|
|
272
|
+
* briefly see two sidebar sessions for the same conversation.
|
|
273
|
+
*
|
|
274
|
+
* This helper lets the synchronizer claim the pending app row first, so the
|
|
275
|
+
* provider id is attached before any watcher-created row exists. The result
|
|
276
|
+
* is simpler than frontend dedupe and keeps the race resolved at the source.
|
|
277
|
+
*/
|
|
278
|
+
findLatestPendingAppSession(provider: string, projectPath: string): SessionRow | null {
|
|
279
|
+
const db = getConnection();
|
|
280
|
+
const normalizedProjectPath = normalizeProjectPathForProvider(provider, projectPath);
|
|
281
|
+
const row = db
|
|
282
|
+
.prepare(
|
|
283
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
284
|
+
FROM sessions
|
|
285
|
+
WHERE provider = ?
|
|
286
|
+
AND project_path = ?
|
|
287
|
+
AND provider_session_id IS NULL
|
|
288
|
+
AND isArchived = 0
|
|
289
|
+
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
|
290
|
+
LIMIT 1`
|
|
291
|
+
)
|
|
292
|
+
.get(provider, normalizedProjectPath) as SessionRow | undefined;
|
|
293
|
+
|
|
294
|
+
return normalizeSessionRow(row) ?? null;
|
|
101
295
|
},
|
|
102
296
|
|
|
103
297
|
getAllSessions(): SessionRow[] {
|
|
104
298
|
const db = getConnection();
|
|
105
|
-
|
|
299
|
+
const rows = db
|
|
106
300
|
.prepare(
|
|
107
|
-
`SELECT
|
|
301
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
108
302
|
FROM sessions
|
|
109
303
|
WHERE isArchived = 0`
|
|
110
304
|
)
|
|
111
305
|
.all() as SessionRow[];
|
|
306
|
+
|
|
307
|
+
return normalizeSessionRows(rows);
|
|
112
308
|
},
|
|
113
309
|
|
|
114
310
|
/**
|
|
@@ -117,27 +313,31 @@ export const sessionsDb = {
|
|
|
117
313
|
*/
|
|
118
314
|
getArchivedSessions(): SessionRow[] {
|
|
119
315
|
const db = getConnection();
|
|
120
|
-
|
|
316
|
+
const rows = db
|
|
121
317
|
.prepare(
|
|
122
|
-
`SELECT
|
|
318
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
123
319
|
FROM sessions
|
|
124
320
|
WHERE isArchived = 1
|
|
125
321
|
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
|
126
322
|
)
|
|
127
323
|
.all() as SessionRow[];
|
|
324
|
+
|
|
325
|
+
return normalizeSessionRows(rows);
|
|
128
326
|
},
|
|
129
327
|
|
|
130
328
|
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
|
131
329
|
const db = getConnection();
|
|
132
330
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
133
|
-
|
|
331
|
+
const rows = db
|
|
134
332
|
.prepare(
|
|
135
|
-
`SELECT
|
|
333
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
136
334
|
FROM sessions
|
|
137
335
|
WHERE project_path = ?
|
|
138
336
|
AND isArchived = 0`
|
|
139
337
|
)
|
|
140
338
|
.all(normalizedProjectPath) as SessionRow[];
|
|
339
|
+
|
|
340
|
+
return normalizeSessionRows(rows);
|
|
141
341
|
},
|
|
142
342
|
|
|
143
343
|
/**
|
|
@@ -147,21 +347,23 @@ export const sessionsDb = {
|
|
|
147
347
|
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
|
148
348
|
const db = getConnection();
|
|
149
349
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
150
|
-
|
|
350
|
+
const rows = db
|
|
151
351
|
.prepare(
|
|
152
|
-
`SELECT
|
|
352
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
153
353
|
FROM sessions
|
|
154
354
|
WHERE project_path = ?`
|
|
155
355
|
)
|
|
156
356
|
.all(normalizedProjectPath) as SessionRow[];
|
|
357
|
+
|
|
358
|
+
return normalizeSessionRows(rows);
|
|
157
359
|
},
|
|
158
360
|
|
|
159
361
|
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
|
160
362
|
const db = getConnection();
|
|
161
363
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
162
|
-
|
|
364
|
+
const rows = db
|
|
163
365
|
.prepare(
|
|
164
|
-
`SELECT
|
|
366
|
+
`SELECT ${SESSION_ROW_COLUMNS}
|
|
165
367
|
FROM sessions
|
|
166
368
|
WHERE project_path = ?
|
|
167
369
|
AND isArchived = 0
|
|
@@ -169,6 +371,8 @@ export const sessionsDb = {
|
|
|
169
371
|
LIMIT ? OFFSET ?`
|
|
170
372
|
)
|
|
171
373
|
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
|
374
|
+
|
|
375
|
+
return normalizeSessionRows(rows);
|
|
172
376
|
},
|
|
173
377
|
|
|
174
378
|
countSessionsByProjectPath(projectPath: string): number {
|
|
@@ -69,6 +69,23 @@ CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
|
69
69
|
);
|
|
70
70
|
`;
|
|
71
71
|
|
|
72
|
+
export const NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL = `
|
|
73
|
+
CREATE TABLE IF NOT EXISTS notification_channel_endpoints (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
user_id INTEGER NOT NULL,
|
|
76
|
+
channel TEXT NOT NULL,
|
|
77
|
+
endpoint_id TEXT NOT NULL,
|
|
78
|
+
label TEXT,
|
|
79
|
+
metadata_json TEXT,
|
|
80
|
+
enabled BOOLEAN DEFAULT 1,
|
|
81
|
+
last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
82
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
UNIQUE(user_id, channel, endpoint_id),
|
|
85
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
86
|
+
);
|
|
87
|
+
`;
|
|
88
|
+
|
|
72
89
|
export const PROJECTS_TABLE_SCHEMA_SQL = `
|
|
73
90
|
CREATE TABLE IF NOT EXISTS projects (
|
|
74
91
|
project_id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -83,6 +100,12 @@ export const SESSIONS_TABLE_SCHEMA_SQL = `
|
|
|
83
100
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
84
101
|
session_id TEXT NOT NULL,
|
|
85
102
|
provider TEXT NOT NULL DEFAULT 'claude',
|
|
103
|
+
-- The session id used by the provider CLI/SDK on disk (JSONL file name,
|
|
104
|
+
-- store.db folder, sqlite row id, ...). \`session_id\` is the stable
|
|
105
|
+
-- app-facing id that the frontend uses for the whole session lifetime;
|
|
106
|
+
-- \`provider_session_id\` is filled in once the provider announces its own
|
|
107
|
+
-- id mid-run, or equals \`session_id\` for sessions discovered on disk.
|
|
108
|
+
provider_session_id TEXT,
|
|
86
109
|
custom_name TEXT,
|
|
87
110
|
project_path TEXT,
|
|
88
111
|
jsonl_path TEXT,
|
|
@@ -138,6 +161,10 @@ ${VAPID_KEYS_TABLE_SCHEMA_SQL}
|
|
|
138
161
|
${PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL}
|
|
139
162
|
CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id);
|
|
140
163
|
|
|
164
|
+
${NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL}
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_user_channel ON notification_channel_endpoints(user_id, channel);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_enabled ON notification_channel_endpoints(enabled);
|
|
167
|
+
|
|
141
168
|
${PROJECTS_TABLE_SCHEMA_SQL}
|
|
142
169
|
-- NOTE: These indexes are created in migrations after legacy table-shape repairs.
|
|
143
170
|
-- Creating them here can fail on upgraded installs where projects lacks those columns.
|