cowork-os 0.3.21
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/LICENSE +21 -0
- package/README.md +1638 -0
- package/bin/cowork.js +42 -0
- package/build/entitlements.mac.plist +16 -0
- package/build/icon.icns +0 -0
- package/build/icon.png +0 -0
- package/dist/electron/electron/activity/ActivityRepository.js +190 -0
- package/dist/electron/electron/agent/browser/browser-service.js +639 -0
- package/dist/electron/electron/agent/context-manager.js +225 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
- package/dist/electron/electron/agent/daemon.js +975 -0
- package/dist/electron/electron/agent/executor.js +3561 -0
- package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
- package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
- package/dist/electron/electron/agent/llm/index.js +34 -0
- package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
- package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
- package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
- package/dist/electron/electron/agent/llm/pricing.js +160 -0
- package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
- package/dist/electron/electron/agent/llm/types.js +178 -0
- package/dist/electron/electron/agent/queue-manager.js +378 -0
- package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
- package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
- package/dist/electron/electron/agent/sandbox/runner.js +410 -0
- package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
- package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
- package/dist/electron/electron/agent/search/brave-provider.js +119 -0
- package/dist/electron/electron/agent/search/google-provider.js +100 -0
- package/dist/electron/electron/agent/search/index.js +28 -0
- package/dist/electron/electron/agent/search/provider-factory.js +395 -0
- package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
- package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
- package/dist/electron/electron/agent/search/types.js +40 -0
- package/dist/electron/electron/agent/security/index.js +12 -0
- package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
- package/dist/electron/electron/agent/security/output-filter.js +217 -0
- package/dist/electron/electron/agent/skill-eligibility.js +281 -0
- package/dist/electron/electron/agent/skill-registry.js +396 -0
- package/dist/electron/electron/agent/skills/document.js +878 -0
- package/dist/electron/electron/agent/skills/image-generator.js +225 -0
- package/dist/electron/electron/agent/skills/organizer.js +141 -0
- package/dist/electron/electron/agent/skills/presentation.js +367 -0
- package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
- package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
- package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
- package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
- package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
- package/dist/electron/electron/agent/tools/file-tools.js +719 -0
- package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
- package/dist/electron/electron/agent/tools/image-tools.js +111 -0
- package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
- package/dist/electron/electron/agent/tools/node-tools.js +476 -0
- package/dist/electron/electron/agent/tools/registry.js +2719 -0
- package/dist/electron/electron/agent/tools/search-tools.js +91 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
- package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
- package/dist/electron/electron/agent/tools/system-tools.js +578 -0
- package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
- package/dist/electron/electron/agent/tools/x-tools.js +264 -0
- package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
- package/dist/electron/electron/agents/HeartbeatService.js +356 -0
- package/dist/electron/electron/agents/MentionRepository.js +197 -0
- package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
- package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
- package/dist/electron/electron/canvas/canvas-manager.js +714 -0
- package/dist/electron/electron/canvas/canvas-preload.js +53 -0
- package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
- package/dist/electron/electron/canvas/canvas-store.js +174 -0
- package/dist/electron/electron/canvas/index.js +13 -0
- package/dist/electron/electron/control-plane/client.js +364 -0
- package/dist/electron/electron/control-plane/handlers.js +572 -0
- package/dist/electron/electron/control-plane/index.js +41 -0
- package/dist/electron/electron/control-plane/node-manager.js +264 -0
- package/dist/electron/electron/control-plane/protocol.js +194 -0
- package/dist/electron/electron/control-plane/remote-client.js +437 -0
- package/dist/electron/electron/control-plane/server.js +640 -0
- package/dist/electron/electron/control-plane/settings.js +369 -0
- package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
- package/dist/electron/electron/cron/index.js +30 -0
- package/dist/electron/electron/cron/schedule.js +190 -0
- package/dist/electron/electron/cron/service.js +614 -0
- package/dist/electron/electron/cron/store.js +155 -0
- package/dist/electron/electron/cron/types.js +82 -0
- package/dist/electron/electron/cron/webhook.js +258 -0
- package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
- package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
- package/dist/electron/electron/database/repositories.js +1781 -0
- package/dist/electron/electron/database/schema.js +978 -0
- package/dist/electron/electron/extensions/index.js +33 -0
- package/dist/electron/electron/extensions/loader.js +313 -0
- package/dist/electron/electron/extensions/registry.js +485 -0
- package/dist/electron/electron/extensions/types.js +11 -0
- package/dist/electron/electron/gateway/channel-registry.js +1102 -0
- package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
- package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
- package/dist/electron/electron/gateway/channels/discord.js +975 -0
- package/dist/electron/electron/gateway/channels/email-client.js +593 -0
- package/dist/electron/electron/gateway/channels/email.js +443 -0
- package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
- package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
- package/dist/electron/electron/gateway/channels/imessage.js +465 -0
- package/dist/electron/electron/gateway/channels/index.js +36 -0
- package/dist/electron/electron/gateway/channels/line-client.js +470 -0
- package/dist/electron/electron/gateway/channels/line.js +479 -0
- package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
- package/dist/electron/electron/gateway/channels/matrix.js +592 -0
- package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
- package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
- package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
- package/dist/electron/electron/gateway/channels/signal.js +582 -0
- package/dist/electron/electron/gateway/channels/slack.js +415 -0
- package/dist/electron/electron/gateway/channels/teams.js +596 -0
- package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
- package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
- package/dist/electron/electron/gateway/channels/twitch.js +396 -0
- package/dist/electron/electron/gateway/channels/types.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
- package/dist/electron/electron/gateway/context-policy.js +268 -0
- package/dist/electron/electron/gateway/index.js +1063 -0
- package/dist/electron/electron/gateway/infrastructure.js +496 -0
- package/dist/electron/electron/gateway/router.js +2700 -0
- package/dist/electron/electron/gateway/security.js +375 -0
- package/dist/electron/electron/gateway/session.js +115 -0
- package/dist/electron/electron/gateway/tunnel.js +503 -0
- package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
- package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
- package/dist/electron/electron/hooks/index.js +46 -0
- package/dist/electron/electron/hooks/mappings.js +381 -0
- package/dist/electron/electron/hooks/server.js +480 -0
- package/dist/electron/electron/hooks/settings.js +447 -0
- package/dist/electron/electron/hooks/types.js +41 -0
- package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
- package/dist/electron/electron/ipc/handlers.js +3138 -0
- package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
- package/dist/electron/electron/main.js +448 -0
- package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
- package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
- package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
- package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
- package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
- package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
- package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
- package/dist/electron/electron/mcp/settings.js +446 -0
- package/dist/electron/electron/mcp/types.js +59 -0
- package/dist/electron/electron/memory/MemoryService.js +435 -0
- package/dist/electron/electron/notifications/index.js +17 -0
- package/dist/electron/electron/notifications/service.js +118 -0
- package/dist/electron/electron/notifications/store.js +144 -0
- package/dist/electron/electron/preload.js +842 -0
- package/dist/electron/electron/reports/StandupReportService.js +272 -0
- package/dist/electron/electron/security/concurrency.js +293 -0
- package/dist/electron/electron/security/index.js +15 -0
- package/dist/electron/electron/security/policy-manager.js +435 -0
- package/dist/electron/electron/settings/appearance-manager.js +193 -0
- package/dist/electron/electron/settings/personality-manager.js +724 -0
- package/dist/electron/electron/settings/x-manager.js +58 -0
- package/dist/electron/electron/tailscale/exposure.js +188 -0
- package/dist/electron/electron/tailscale/index.js +28 -0
- package/dist/electron/electron/tailscale/settings.js +205 -0
- package/dist/electron/electron/tailscale/tailscale.js +355 -0
- package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
- package/dist/electron/electron/tray/TrayManager.js +895 -0
- package/dist/electron/electron/tray/index.js +9 -0
- package/dist/electron/electron/updater/index.js +6 -0
- package/dist/electron/electron/updater/update-manager.js +418 -0
- package/dist/electron/electron/utils/env-migration.js +209 -0
- package/dist/electron/electron/utils/process.js +102 -0
- package/dist/electron/electron/utils/rate-limiter.js +104 -0
- package/dist/electron/electron/utils/validation.js +419 -0
- package/dist/electron/electron/utils/x-cli.js +177 -0
- package/dist/electron/electron/voice/VoiceService.js +507 -0
- package/dist/electron/electron/voice/index.js +14 -0
- package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
- package/dist/electron/shared/channelMessages.js +170 -0
- package/dist/electron/shared/types.js +1185 -0
- package/package.json +159 -0
- package/resources/skills/1password.json +10 -0
- package/resources/skills/add-documentation.json +31 -0
- package/resources/skills/analyze-csv.json +17 -0
- package/resources/skills/apple-notes.json +10 -0
- package/resources/skills/apple-reminders.json +10 -0
- package/resources/skills/auto-commenter.json +10 -0
- package/resources/skills/bear-notes.json +10 -0
- package/resources/skills/bird.json +35 -0
- package/resources/skills/blogwatcher.json +10 -0
- package/resources/skills/blucli.json +10 -0
- package/resources/skills/bluebubbles.json +10 -0
- package/resources/skills/camsnap.json +10 -0
- package/resources/skills/clean-imports.json +18 -0
- package/resources/skills/code-review.json +18 -0
- package/resources/skills/coding-agent.json +10 -0
- package/resources/skills/compare-files.json +23 -0
- package/resources/skills/convert-code.json +34 -0
- package/resources/skills/create-changelog.json +24 -0
- package/resources/skills/debug-error.json +17 -0
- package/resources/skills/dependency-check.json +10 -0
- package/resources/skills/discord.json +10 -0
- package/resources/skills/eightctl.json +10 -0
- package/resources/skills/explain-code.json +29 -0
- package/resources/skills/extract-todos.json +18 -0
- package/resources/skills/food-order.json +10 -0
- package/resources/skills/gemini.json +10 -0
- package/resources/skills/generate-readme.json +10 -0
- package/resources/skills/gifgrep.json +10 -0
- package/resources/skills/git-commit.json +10 -0
- package/resources/skills/github.json +10 -0
- package/resources/skills/gog.json +10 -0
- package/resources/skills/goplaces.json +10 -0
- package/resources/skills/himalaya.json +10 -0
- package/resources/skills/imsg.json +10 -0
- package/resources/skills/karpathy-guidelines.json +12 -0
- package/resources/skills/last30days.json +26 -0
- package/resources/skills/local-places.json +10 -0
- package/resources/skills/mcporter.json +10 -0
- package/resources/skills/model-usage.json +10 -0
- package/resources/skills/nano-banana-pro.json +10 -0
- package/resources/skills/nano-pdf.json +10 -0
- package/resources/skills/notion.json +10 -0
- package/resources/skills/obsidian.json +10 -0
- package/resources/skills/openai-image-gen.json +10 -0
- package/resources/skills/openai-whisper-api.json +10 -0
- package/resources/skills/openai-whisper.json +10 -0
- package/resources/skills/openhue.json +10 -0
- package/resources/skills/oracle.json +10 -0
- package/resources/skills/ordercli.json +10 -0
- package/resources/skills/peekaboo.json +10 -0
- package/resources/skills/project-structure.json +10 -0
- package/resources/skills/proofread.json +17 -0
- package/resources/skills/refactor-code.json +31 -0
- package/resources/skills/rename-symbol.json +23 -0
- package/resources/skills/sag.json +10 -0
- package/resources/skills/security-audit.json +18 -0
- package/resources/skills/session-logs.json +10 -0
- package/resources/skills/sherpa-onnx-tts.json +10 -0
- package/resources/skills/skill-creator.json +15 -0
- package/resources/skills/skill-hub.json +29 -0
- package/resources/skills/slack.json +10 -0
- package/resources/skills/songsee.json +10 -0
- package/resources/skills/sonoscli.json +10 -0
- package/resources/skills/spotify-player.json +10 -0
- package/resources/skills/startup-cfo.json +55 -0
- package/resources/skills/summarize-folder.json +18 -0
- package/resources/skills/summarize.json +10 -0
- package/resources/skills/things-mac.json +10 -0
- package/resources/skills/tmux.json +10 -0
- package/resources/skills/translate.json +36 -0
- package/resources/skills/trello.json +10 -0
- package/resources/skills/video-frames.json +10 -0
- package/resources/skills/voice-call.json +10 -0
- package/resources/skills/wacli.json +10 -0
- package/resources/skills/weather.json +10 -0
- package/resources/skills/write-tests.json +31 -0
- package/src/electron/activity/ActivityRepository.ts +238 -0
- package/src/electron/agent/browser/browser-service.ts +721 -0
- package/src/electron/agent/context-manager.ts +257 -0
- package/src/electron/agent/custom-skill-loader.ts +634 -0
- package/src/electron/agent/daemon.ts +1097 -0
- package/src/electron/agent/executor.ts +4017 -0
- package/src/electron/agent/llm/anthropic-provider.ts +175 -0
- package/src/electron/agent/llm/bedrock-provider.ts +236 -0
- package/src/electron/agent/llm/gemini-provider.ts +422 -0
- package/src/electron/agent/llm/index.ts +9 -0
- package/src/electron/agent/llm/ollama-provider.ts +347 -0
- package/src/electron/agent/llm/openai-oauth.ts +127 -0
- package/src/electron/agent/llm/openai-provider.ts +686 -0
- package/src/electron/agent/llm/openrouter-provider.ts +273 -0
- package/src/electron/agent/llm/pricing.ts +180 -0
- package/src/electron/agent/llm/provider-factory.ts +971 -0
- package/src/electron/agent/llm/types.ts +291 -0
- package/src/electron/agent/queue-manager.ts +408 -0
- package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
- package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
- package/src/electron/agent/sandbox/runner.ts +453 -0
- package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
- package/src/electron/agent/sandbox/security-utils.ts +251 -0
- package/src/electron/agent/search/brave-provider.ts +141 -0
- package/src/electron/agent/search/google-provider.ts +131 -0
- package/src/electron/agent/search/index.ts +6 -0
- package/src/electron/agent/search/provider-factory.ts +450 -0
- package/src/electron/agent/search/serpapi-provider.ts +138 -0
- package/src/electron/agent/search/tavily-provider.ts +108 -0
- package/src/electron/agent/search/types.ts +118 -0
- package/src/electron/agent/security/index.ts +20 -0
- package/src/electron/agent/security/input-sanitizer.ts +380 -0
- package/src/electron/agent/security/output-filter.ts +259 -0
- package/src/electron/agent/skill-eligibility.ts +334 -0
- package/src/electron/agent/skill-registry.ts +457 -0
- package/src/electron/agent/skills/document.ts +1070 -0
- package/src/electron/agent/skills/image-generator.ts +272 -0
- package/src/electron/agent/skills/organizer.ts +131 -0
- package/src/electron/agent/skills/presentation.ts +418 -0
- package/src/electron/agent/skills/spreadsheet.ts +166 -0
- package/src/electron/agent/tools/browser-tools.ts +546 -0
- package/src/electron/agent/tools/builtin-settings.ts +422 -0
- package/src/electron/agent/tools/canvas-tools.ts +572 -0
- package/src/electron/agent/tools/cron-tools.ts +723 -0
- package/src/electron/agent/tools/edit-tools.ts +196 -0
- package/src/electron/agent/tools/file-tools.ts +811 -0
- package/src/electron/agent/tools/glob-tools.ts +303 -0
- package/src/electron/agent/tools/grep-tools.ts +432 -0
- package/src/electron/agent/tools/image-tools.ts +126 -0
- package/src/electron/agent/tools/mention-tools.ts +371 -0
- package/src/electron/agent/tools/node-tools.ts +550 -0
- package/src/electron/agent/tools/registry.ts +3052 -0
- package/src/electron/agent/tools/search-tools.ts +111 -0
- package/src/electron/agent/tools/shell-tools.ts +651 -0
- package/src/electron/agent/tools/skill-tools.ts +340 -0
- package/src/electron/agent/tools/system-tools.ts +665 -0
- package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
- package/src/electron/agent/tools/x-tools.ts +267 -0
- package/src/electron/agents/AgentRoleRepository.ts +557 -0
- package/src/electron/agents/HeartbeatService.ts +469 -0
- package/src/electron/agents/MentionRepository.ts +242 -0
- package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
- package/src/electron/agents/WorkingStateRepository.ts +278 -0
- package/src/electron/canvas/canvas-manager.ts +818 -0
- package/src/electron/canvas/canvas-preload.ts +102 -0
- package/src/electron/canvas/canvas-protocol.ts +174 -0
- package/src/electron/canvas/canvas-store.ts +200 -0
- package/src/electron/canvas/index.ts +8 -0
- package/src/electron/control-plane/client.ts +527 -0
- package/src/electron/control-plane/handlers.ts +723 -0
- package/src/electron/control-plane/index.ts +51 -0
- package/src/electron/control-plane/node-manager.ts +322 -0
- package/src/electron/control-plane/protocol.ts +269 -0
- package/src/electron/control-plane/remote-client.ts +517 -0
- package/src/electron/control-plane/server.ts +853 -0
- package/src/electron/control-plane/settings.ts +401 -0
- package/src/electron/control-plane/ssh-tunnel.ts +624 -0
- package/src/electron/cron/index.ts +9 -0
- package/src/electron/cron/schedule.ts +217 -0
- package/src/electron/cron/service.ts +743 -0
- package/src/electron/cron/store.ts +165 -0
- package/src/electron/cron/types.ts +291 -0
- package/src/electron/cron/webhook.ts +303 -0
- package/src/electron/database/SecureSettingsRepository.ts +514 -0
- package/src/electron/database/TaskLabelRepository.ts +148 -0
- package/src/electron/database/repositories.ts +2397 -0
- package/src/electron/database/schema.ts +1017 -0
- package/src/electron/extensions/index.ts +18 -0
- package/src/electron/extensions/loader.ts +336 -0
- package/src/electron/extensions/registry.ts +546 -0
- package/src/electron/extensions/types.ts +372 -0
- package/src/electron/gateway/channel-registry.ts +1267 -0
- package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
- package/src/electron/gateway/channels/bluebubbles.ts +509 -0
- package/src/electron/gateway/channels/discord.ts +1150 -0
- package/src/electron/gateway/channels/email-client.ts +708 -0
- package/src/electron/gateway/channels/email.ts +516 -0
- package/src/electron/gateway/channels/google-chat.ts +760 -0
- package/src/electron/gateway/channels/imessage-client.ts +473 -0
- package/src/electron/gateway/channels/imessage.ts +520 -0
- package/src/electron/gateway/channels/index.ts +21 -0
- package/src/electron/gateway/channels/line-client.ts +598 -0
- package/src/electron/gateway/channels/line.ts +559 -0
- package/src/electron/gateway/channels/matrix-client.ts +632 -0
- package/src/electron/gateway/channels/matrix.ts +655 -0
- package/src/electron/gateway/channels/mattermost-client.ts +526 -0
- package/src/electron/gateway/channels/mattermost.ts +550 -0
- package/src/electron/gateway/channels/signal-client.ts +722 -0
- package/src/electron/gateway/channels/signal.ts +666 -0
- package/src/electron/gateway/channels/slack.ts +458 -0
- package/src/electron/gateway/channels/teams.ts +681 -0
- package/src/electron/gateway/channels/telegram.ts +1727 -0
- package/src/electron/gateway/channels/twitch-client.ts +665 -0
- package/src/electron/gateway/channels/twitch.ts +468 -0
- package/src/electron/gateway/channels/types.ts +1002 -0
- package/src/electron/gateway/channels/whatsapp.ts +1101 -0
- package/src/electron/gateway/context-policy.ts +382 -0
- package/src/electron/gateway/index.ts +1274 -0
- package/src/electron/gateway/infrastructure.ts +645 -0
- package/src/electron/gateway/router.ts +3206 -0
- package/src/electron/gateway/security.ts +422 -0
- package/src/electron/gateway/session.ts +144 -0
- package/src/electron/gateway/tunnel.ts +626 -0
- package/src/electron/guardrails/guardrail-manager.ts +380 -0
- package/src/electron/hooks/gmail-watcher.ts +355 -0
- package/src/electron/hooks/index.ts +30 -0
- package/src/electron/hooks/mappings.ts +404 -0
- package/src/electron/hooks/server.ts +574 -0
- package/src/electron/hooks/settings.ts +466 -0
- package/src/electron/hooks/types.ts +245 -0
- package/src/electron/ipc/canvas-handlers.ts +223 -0
- package/src/electron/ipc/handlers.ts +3661 -0
- package/src/electron/ipc/mission-control-handlers.ts +182 -0
- package/src/electron/main.ts +496 -0
- package/src/electron/mcp/client/MCPClientManager.ts +406 -0
- package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
- package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
- package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
- package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
- package/src/electron/mcp/host/MCPHostServer.ts +388 -0
- package/src/electron/mcp/host/ToolAdapter.ts +140 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
- package/src/electron/mcp/settings.ts +468 -0
- package/src/electron/mcp/types.ts +371 -0
- package/src/electron/memory/MemoryService.ts +523 -0
- package/src/electron/notifications/index.ts +16 -0
- package/src/electron/notifications/service.ts +161 -0
- package/src/electron/notifications/store.ts +163 -0
- package/src/electron/preload.ts +2845 -0
- package/src/electron/reports/StandupReportService.ts +356 -0
- package/src/electron/security/concurrency.ts +333 -0
- package/src/electron/security/index.ts +17 -0
- package/src/electron/security/policy-manager.ts +539 -0
- package/src/electron/settings/appearance-manager.ts +182 -0
- package/src/electron/settings/personality-manager.ts +800 -0
- package/src/electron/settings/x-manager.ts +62 -0
- package/src/electron/tailscale/exposure.ts +262 -0
- package/src/electron/tailscale/index.ts +34 -0
- package/src/electron/tailscale/settings.ts +218 -0
- package/src/electron/tailscale/tailscale.ts +379 -0
- package/src/electron/tray/QuickInputWindow.ts +609 -0
- package/src/electron/tray/TrayManager.ts +1005 -0
- package/src/electron/tray/index.ts +6 -0
- package/src/electron/updater/index.ts +1 -0
- package/src/electron/updater/update-manager.ts +447 -0
- package/src/electron/utils/env-migration.ts +203 -0
- package/src/electron/utils/process.ts +124 -0
- package/src/electron/utils/rate-limiter.ts +130 -0
- package/src/electron/utils/validation.ts +493 -0
- package/src/electron/utils/x-cli.ts +198 -0
- package/src/electron/voice/VoiceService.ts +583 -0
- package/src/electron/voice/index.ts +9 -0
- package/src/electron/voice/voice-settings-manager.ts +403 -0
- package/src/renderer/App.tsx +775 -0
- package/src/renderer/components/ActivityFeed.tsx +407 -0
- package/src/renderer/components/ActivityFeedItem.tsx +285 -0
- package/src/renderer/components/AgentRoleCard.tsx +343 -0
- package/src/renderer/components/AgentRoleEditor.tsx +805 -0
- package/src/renderer/components/AgentSquadSettings.tsx +295 -0
- package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
- package/src/renderer/components/AppearanceSettings.tsx +122 -0
- package/src/renderer/components/ApprovalDialog.tsx +100 -0
- package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
- package/src/renderer/components/CanvasPreview.tsx +1189 -0
- package/src/renderer/components/CommandOutput.tsx +202 -0
- package/src/renderer/components/ContextPolicySettings.tsx +523 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
- package/src/renderer/components/DisclaimerModal.tsx +124 -0
- package/src/renderer/components/DiscordSettings.tsx +436 -0
- package/src/renderer/components/EmailSettings.tsx +606 -0
- package/src/renderer/components/ExtensionsSettings.tsx +542 -0
- package/src/renderer/components/FileViewer.tsx +224 -0
- package/src/renderer/components/GoogleChatSettings.tsx +535 -0
- package/src/renderer/components/GuardrailSettings.tsx +487 -0
- package/src/renderer/components/HooksSettings.tsx +581 -0
- package/src/renderer/components/ImessageSettings.tsx +484 -0
- package/src/renderer/components/LineSettings.tsx +483 -0
- package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
- package/src/renderer/components/MCPSettings.tsx +943 -0
- package/src/renderer/components/MainContent.tsx +2433 -0
- package/src/renderer/components/MatrixSettings.tsx +510 -0
- package/src/renderer/components/MattermostSettings.tsx +473 -0
- package/src/renderer/components/MemorySettings.tsx +247 -0
- package/src/renderer/components/MentionBadge.tsx +87 -0
- package/src/renderer/components/MentionInput.tsx +409 -0
- package/src/renderer/components/MentionList.tsx +476 -0
- package/src/renderer/components/MissionControlPanel.tsx +1995 -0
- package/src/renderer/components/NodesSettings.tsx +316 -0
- package/src/renderer/components/NotificationPanel.tsx +481 -0
- package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
- package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
- package/src/renderer/components/Onboarding/index.ts +3 -0
- package/src/renderer/components/OnboardingModal.tsx +698 -0
- package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
- package/src/renderer/components/PersonalitySettings.tsx +597 -0
- package/src/renderer/components/QueueSettings.tsx +119 -0
- package/src/renderer/components/QuickTaskFAB.tsx +71 -0
- package/src/renderer/components/RightPanel.tsx +413 -0
- package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
- package/src/renderer/components/SearchSettings.tsx +328 -0
- package/src/renderer/components/Settings.tsx +1504 -0
- package/src/renderer/components/Sidebar.tsx +344 -0
- package/src/renderer/components/SignalSettings.tsx +673 -0
- package/src/renderer/components/SkillHubBrowser.tsx +458 -0
- package/src/renderer/components/SkillParameterModal.tsx +185 -0
- package/src/renderer/components/SkillsSettings.tsx +451 -0
- package/src/renderer/components/SlackSettings.tsx +442 -0
- package/src/renderer/components/StandupReportViewer.tsx +614 -0
- package/src/renderer/components/TaskBoard.tsx +498 -0
- package/src/renderer/components/TaskBoardCard.tsx +357 -0
- package/src/renderer/components/TaskBoardColumn.tsx +211 -0
- package/src/renderer/components/TaskLabelManager.tsx +472 -0
- package/src/renderer/components/TaskQueuePanel.tsx +144 -0
- package/src/renderer/components/TaskQuickActions.tsx +492 -0
- package/src/renderer/components/TaskTimeline.tsx +216 -0
- package/src/renderer/components/TaskView.tsx +162 -0
- package/src/renderer/components/TeamsSettings.tsx +518 -0
- package/src/renderer/components/TelegramSettings.tsx +421 -0
- package/src/renderer/components/Toast.tsx +76 -0
- package/src/renderer/components/TraySettings.tsx +189 -0
- package/src/renderer/components/TwitchSettings.tsx +511 -0
- package/src/renderer/components/UpdateSettings.tsx +295 -0
- package/src/renderer/components/VoiceIndicator.tsx +270 -0
- package/src/renderer/components/VoiceSettings.tsx +867 -0
- package/src/renderer/components/WhatsAppSettings.tsx +721 -0
- package/src/renderer/components/WorkingStateEditor.tsx +309 -0
- package/src/renderer/components/WorkingStateHistory.tsx +481 -0
- package/src/renderer/components/WorkspaceSelector.tsx +150 -0
- package/src/renderer/components/XSettings.tsx +311 -0
- package/src/renderer/global.d.ts +9 -0
- package/src/renderer/hooks/useAgentContext.ts +153 -0
- package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
- package/src/renderer/hooks/useVoiceInput.ts +268 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +10 -0
- package/src/renderer/public/cowork-os-logo.png +0 -0
- package/src/renderer/quick-input.html +164 -0
- package/src/renderer/styles/index.css +14504 -0
- package/src/renderer/utils/agentMessages.ts +749 -0
- package/src/renderer/utils/voice-directives.ts +169 -0
- package/src/shared/channelMessages.ts +213 -0
- package/src/shared/types.ts +3608 -0
- package/tsconfig.electron.json +26 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,1781 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemorySettingsRepository = exports.MemorySummaryRepository = exports.MemoryRepository = exports.AuditLogRepository = exports.RateLimitRepository = exports.DeliveryTrackingRepository = exports.ScheduledMessageRepository = exports.MessageQueueRepository = exports.ChannelMessageRepository = exports.ChannelSessionRepository = exports.ChannelUserRepository = exports.ChannelRepository = exports.LLMModelRepository = exports.SkillRepository = exports.ApprovalRepository = exports.ArtifactRepository = exports.TaskEventRepository = exports.TaskRepository = exports.WorkspaceRepository = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
/**
|
|
6
|
+
* Safely parse JSON with error handling
|
|
7
|
+
* Returns defaultValue if parsing fails
|
|
8
|
+
*/
|
|
9
|
+
function safeJsonParse(jsonString, defaultValue, context) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(jsonString);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
console.error(`Failed to parse JSON${context ? ` in ${context}` : ''}:`, error, 'Input:', jsonString?.slice(0, 100));
|
|
15
|
+
return defaultValue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class WorkspaceRepository {
|
|
19
|
+
constructor(db) {
|
|
20
|
+
this.db = db;
|
|
21
|
+
}
|
|
22
|
+
create(name, path, permissions) {
|
|
23
|
+
const workspace = {
|
|
24
|
+
id: (0, uuid_1.v4)(),
|
|
25
|
+
name,
|
|
26
|
+
path,
|
|
27
|
+
createdAt: Date.now(),
|
|
28
|
+
permissions,
|
|
29
|
+
};
|
|
30
|
+
const stmt = this.db.prepare(`
|
|
31
|
+
INSERT INTO workspaces (id, name, path, created_at, permissions)
|
|
32
|
+
VALUES (?, ?, ?, ?, ?)
|
|
33
|
+
`);
|
|
34
|
+
stmt.run(workspace.id, workspace.name, workspace.path, workspace.createdAt, JSON.stringify(workspace.permissions));
|
|
35
|
+
return workspace;
|
|
36
|
+
}
|
|
37
|
+
findById(id) {
|
|
38
|
+
const stmt = this.db.prepare('SELECT * FROM workspaces WHERE id = ?');
|
|
39
|
+
const row = stmt.get(id);
|
|
40
|
+
return row ? this.mapRowToWorkspace(row) : undefined;
|
|
41
|
+
}
|
|
42
|
+
findAll() {
|
|
43
|
+
const stmt = this.db.prepare('SELECT * FROM workspaces ORDER BY created_at DESC');
|
|
44
|
+
const rows = stmt.all();
|
|
45
|
+
return rows.map(row => this.mapRowToWorkspace(row));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a workspace with the given path already exists
|
|
49
|
+
*/
|
|
50
|
+
existsByPath(path) {
|
|
51
|
+
const stmt = this.db.prepare('SELECT 1 FROM workspaces WHERE path = ?');
|
|
52
|
+
const row = stmt.get(path);
|
|
53
|
+
return !!row;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Find a workspace by its path
|
|
57
|
+
*/
|
|
58
|
+
findByPath(path) {
|
|
59
|
+
const stmt = this.db.prepare('SELECT * FROM workspaces WHERE path = ?');
|
|
60
|
+
const row = stmt.get(path);
|
|
61
|
+
return row ? this.mapRowToWorkspace(row) : undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update workspace permissions
|
|
65
|
+
*/
|
|
66
|
+
updatePermissions(id, permissions) {
|
|
67
|
+
const stmt = this.db.prepare('UPDATE workspaces SET permissions = ? WHERE id = ?');
|
|
68
|
+
stmt.run(JSON.stringify(permissions), id);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Delete a workspace by ID
|
|
72
|
+
*/
|
|
73
|
+
delete(id) {
|
|
74
|
+
const stmt = this.db.prepare('DELETE FROM workspaces WHERE id = ?');
|
|
75
|
+
stmt.run(id);
|
|
76
|
+
}
|
|
77
|
+
mapRowToWorkspace(row) {
|
|
78
|
+
// Note: network is true by default for browser tools (web access)
|
|
79
|
+
const defaultPermissions = { read: true, write: true, delete: false, network: true, shell: false };
|
|
80
|
+
const storedPermissions = safeJsonParse(row.permissions, defaultPermissions, 'workspace.permissions');
|
|
81
|
+
// Merge with defaults to ensure new fields (like network) get proper defaults
|
|
82
|
+
// for workspaces created before those fields existed
|
|
83
|
+
const mergedPermissions = {
|
|
84
|
+
...defaultPermissions,
|
|
85
|
+
...storedPermissions,
|
|
86
|
+
};
|
|
87
|
+
// Migration: if network was explicitly false (old default), upgrade it to true
|
|
88
|
+
// This ensures existing workspaces get browser tool access
|
|
89
|
+
if (storedPermissions.network === false) {
|
|
90
|
+
mergedPermissions.network = true;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
id: row.id,
|
|
94
|
+
name: row.name,
|
|
95
|
+
path: row.path,
|
|
96
|
+
createdAt: row.created_at,
|
|
97
|
+
permissions: mergedPermissions,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.WorkspaceRepository = WorkspaceRepository;
|
|
102
|
+
class TaskRepository {
|
|
103
|
+
constructor(db) {
|
|
104
|
+
this.db = db;
|
|
105
|
+
}
|
|
106
|
+
create(task) {
|
|
107
|
+
const newTask = {
|
|
108
|
+
...task,
|
|
109
|
+
id: (0, uuid_1.v4)(),
|
|
110
|
+
createdAt: Date.now(),
|
|
111
|
+
updatedAt: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
const stmt = this.db.prepare(`
|
|
114
|
+
INSERT INTO tasks (id, title, prompt, status, workspace_id, created_at, updated_at, budget_tokens, budget_cost, success_criteria, max_attempts, current_attempt, parent_task_id, agent_type, agent_config, depth, result_summary)
|
|
115
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
116
|
+
`);
|
|
117
|
+
stmt.run(newTask.id, newTask.title, newTask.prompt, newTask.status, newTask.workspaceId, newTask.createdAt, newTask.updatedAt, newTask.budgetTokens || null, newTask.budgetCost || null, newTask.successCriteria ? JSON.stringify(newTask.successCriteria) : null, newTask.maxAttempts || null, newTask.currentAttempt || 1, newTask.parentTaskId || null, newTask.agentType || 'main', newTask.agentConfig ? JSON.stringify(newTask.agentConfig) : null, newTask.depth ?? 0, newTask.resultSummary || null);
|
|
118
|
+
return newTask;
|
|
119
|
+
}
|
|
120
|
+
update(id, updates) {
|
|
121
|
+
const fields = [];
|
|
122
|
+
const values = [];
|
|
123
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
124
|
+
// Validate field name against whitelist
|
|
125
|
+
if (!TaskRepository.ALLOWED_UPDATE_FIELDS.has(key)) {
|
|
126
|
+
console.warn(`Ignoring unknown field in task update: ${key}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
130
|
+
fields.push(`${snakeKey} = ?`);
|
|
131
|
+
// JSON serialize object/array fields
|
|
132
|
+
if ((key === 'successCriteria' || key === 'agentConfig' || key === 'labels' || key === 'mentionedAgentRoleIds') && value != null) {
|
|
133
|
+
values.push(JSON.stringify(value));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
values.push(value);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (fields.length === 0) {
|
|
140
|
+
return; // No valid fields to update
|
|
141
|
+
}
|
|
142
|
+
fields.push('updated_at = ?');
|
|
143
|
+
values.push(Date.now());
|
|
144
|
+
values.push(id);
|
|
145
|
+
const stmt = this.db.prepare(`UPDATE tasks SET ${fields.join(', ')} WHERE id = ?`);
|
|
146
|
+
stmt.run(...values);
|
|
147
|
+
}
|
|
148
|
+
findById(id) {
|
|
149
|
+
const stmt = this.db.prepare('SELECT * FROM tasks WHERE id = ?');
|
|
150
|
+
const row = stmt.get(id);
|
|
151
|
+
return row ? this.mapRowToTask(row) : undefined;
|
|
152
|
+
}
|
|
153
|
+
findAll(limit = 100, offset = 0) {
|
|
154
|
+
const stmt = this.db.prepare(`
|
|
155
|
+
SELECT * FROM tasks
|
|
156
|
+
ORDER BY created_at DESC
|
|
157
|
+
LIMIT ? OFFSET ?
|
|
158
|
+
`);
|
|
159
|
+
const rows = stmt.all(limit, offset);
|
|
160
|
+
return rows.map(row => this.mapRowToTask(row));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Find tasks by status (single status or array of statuses)
|
|
164
|
+
*/
|
|
165
|
+
findByStatus(status) {
|
|
166
|
+
const statuses = Array.isArray(status) ? status : [status];
|
|
167
|
+
const placeholders = statuses.map(() => '?').join(', ');
|
|
168
|
+
const stmt = this.db.prepare(`
|
|
169
|
+
SELECT * FROM tasks
|
|
170
|
+
WHERE status IN (${placeholders})
|
|
171
|
+
ORDER BY created_at ASC
|
|
172
|
+
`);
|
|
173
|
+
const rows = stmt.all(...statuses);
|
|
174
|
+
return rows.map(row => this.mapRowToTask(row));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Find tasks by workspace ID
|
|
178
|
+
*/
|
|
179
|
+
findByWorkspace(workspaceId) {
|
|
180
|
+
const stmt = this.db.prepare(`
|
|
181
|
+
SELECT * FROM tasks
|
|
182
|
+
WHERE workspace_id = ?
|
|
183
|
+
ORDER BY created_at DESC
|
|
184
|
+
`);
|
|
185
|
+
const rows = stmt.all(workspaceId);
|
|
186
|
+
return rows.map(row => this.mapRowToTask(row));
|
|
187
|
+
}
|
|
188
|
+
delete(id) {
|
|
189
|
+
// Use transaction to ensure atomic deletion
|
|
190
|
+
const deleteTransaction = this.db.transaction((taskId) => {
|
|
191
|
+
// Delete related records from all tables with foreign keys to tasks
|
|
192
|
+
const deleteEvents = this.db.prepare('DELETE FROM task_events WHERE task_id = ?');
|
|
193
|
+
deleteEvents.run(taskId);
|
|
194
|
+
const deleteArtifacts = this.db.prepare('DELETE FROM artifacts WHERE task_id = ?');
|
|
195
|
+
deleteArtifacts.run(taskId);
|
|
196
|
+
const deleteApprovals = this.db.prepare('DELETE FROM approvals WHERE task_id = ?');
|
|
197
|
+
deleteApprovals.run(taskId);
|
|
198
|
+
// Delete activity feed entries for this task
|
|
199
|
+
const deleteActivities = this.db.prepare('DELETE FROM activity_feed WHERE task_id = ?');
|
|
200
|
+
deleteActivities.run(taskId);
|
|
201
|
+
// Delete agent mentions for this task
|
|
202
|
+
const deleteMentions = this.db.prepare('DELETE FROM agent_mentions WHERE task_id = ?');
|
|
203
|
+
deleteMentions.run(taskId);
|
|
204
|
+
// Delete working state entries for this task
|
|
205
|
+
const deleteWorkingState = this.db.prepare('DELETE FROM agent_working_state WHERE task_id = ?');
|
|
206
|
+
deleteWorkingState.run(taskId);
|
|
207
|
+
// Nullify task_id in memories rather than deleting them
|
|
208
|
+
const clearMemoryTaskId = this.db.prepare('UPDATE memories SET task_id = NULL WHERE task_id = ?');
|
|
209
|
+
clearMemoryTaskId.run(taskId);
|
|
210
|
+
// Nullify task_id in channel_sessions rather than deleting the session
|
|
211
|
+
const clearSessionTaskId = this.db.prepare('UPDATE channel_sessions SET task_id = NULL WHERE task_id = ?');
|
|
212
|
+
clearSessionTaskId.run(taskId);
|
|
213
|
+
// Finally delete the task
|
|
214
|
+
const deleteTask = this.db.prepare('DELETE FROM tasks WHERE id = ?');
|
|
215
|
+
deleteTask.run(taskId);
|
|
216
|
+
});
|
|
217
|
+
deleteTransaction(id);
|
|
218
|
+
}
|
|
219
|
+
mapRowToTask(row) {
|
|
220
|
+
return {
|
|
221
|
+
id: row.id,
|
|
222
|
+
title: row.title,
|
|
223
|
+
prompt: row.prompt,
|
|
224
|
+
status: row.status,
|
|
225
|
+
workspaceId: row.workspace_id,
|
|
226
|
+
createdAt: row.created_at,
|
|
227
|
+
updatedAt: row.updated_at,
|
|
228
|
+
completedAt: row.completed_at || undefined,
|
|
229
|
+
budgetTokens: row.budget_tokens || undefined,
|
|
230
|
+
budgetCost: row.budget_cost || undefined,
|
|
231
|
+
error: row.error || undefined,
|
|
232
|
+
// Goal Mode fields
|
|
233
|
+
successCriteria: row.success_criteria ? safeJsonParse(row.success_criteria, undefined, 'task.successCriteria') : undefined,
|
|
234
|
+
maxAttempts: row.max_attempts || undefined,
|
|
235
|
+
currentAttempt: row.current_attempt || undefined,
|
|
236
|
+
// Sub-Agent / Parallel Agent fields
|
|
237
|
+
parentTaskId: row.parent_task_id || undefined,
|
|
238
|
+
agentType: row.agent_type || undefined,
|
|
239
|
+
agentConfig: row.agent_config ? safeJsonParse(row.agent_config, undefined, 'task.agentConfig') : undefined,
|
|
240
|
+
depth: row.depth ?? undefined,
|
|
241
|
+
resultSummary: row.result_summary || undefined,
|
|
242
|
+
// Agent Squad fields
|
|
243
|
+
assignedAgentRoleId: row.assigned_agent_role_id || undefined,
|
|
244
|
+
boardColumn: row.board_column || undefined,
|
|
245
|
+
priority: row.priority ?? undefined,
|
|
246
|
+
// Task Board fields
|
|
247
|
+
labels: row.labels ? safeJsonParse(row.labels, [], 'task.labels') : undefined,
|
|
248
|
+
dueDate: row.due_date || undefined,
|
|
249
|
+
estimatedMinutes: row.estimated_minutes || undefined,
|
|
250
|
+
actualMinutes: row.actual_minutes || undefined,
|
|
251
|
+
mentionedAgentRoleIds: row.mentioned_agent_role_ids ? safeJsonParse(row.mentioned_agent_role_ids, [], 'task.mentionedAgentRoleIds') : undefined,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Find tasks by parent task ID
|
|
256
|
+
*/
|
|
257
|
+
findByParent(parentTaskId) {
|
|
258
|
+
const stmt = this.db.prepare(`
|
|
259
|
+
SELECT * FROM tasks
|
|
260
|
+
WHERE parent_task_id = ?
|
|
261
|
+
ORDER BY created_at ASC
|
|
262
|
+
`);
|
|
263
|
+
const rows = stmt.all(parentTaskId);
|
|
264
|
+
return rows.map(row => this.mapRowToTask(row));
|
|
265
|
+
}
|
|
266
|
+
// ============ Task Board Methods ============
|
|
267
|
+
/**
|
|
268
|
+
* Find tasks by workspace and board column
|
|
269
|
+
*/
|
|
270
|
+
findByBoardColumn(workspaceId, boardColumn) {
|
|
271
|
+
const stmt = this.db.prepare(`
|
|
272
|
+
SELECT * FROM tasks
|
|
273
|
+
WHERE workspace_id = ? AND board_column = ?
|
|
274
|
+
ORDER BY priority DESC, created_at ASC
|
|
275
|
+
`);
|
|
276
|
+
const rows = stmt.all(workspaceId, boardColumn);
|
|
277
|
+
return rows.map(row => this.mapRowToTask(row));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get tasks grouped by board column for a workspace
|
|
281
|
+
*/
|
|
282
|
+
getTaskBoard(workspaceId) {
|
|
283
|
+
const stmt = this.db.prepare(`
|
|
284
|
+
SELECT * FROM tasks
|
|
285
|
+
WHERE workspace_id = ? AND parent_task_id IS NULL
|
|
286
|
+
ORDER BY board_column, priority DESC, created_at ASC
|
|
287
|
+
`);
|
|
288
|
+
const rows = stmt.all(workspaceId);
|
|
289
|
+
const tasks = rows.map(row => this.mapRowToTask(row));
|
|
290
|
+
// Group tasks by board column
|
|
291
|
+
const board = {
|
|
292
|
+
backlog: [],
|
|
293
|
+
todo: [],
|
|
294
|
+
in_progress: [],
|
|
295
|
+
review: [],
|
|
296
|
+
done: [],
|
|
297
|
+
};
|
|
298
|
+
for (const task of tasks) {
|
|
299
|
+
const column = task.boardColumn || 'backlog';
|
|
300
|
+
if (board[column]) {
|
|
301
|
+
board[column].push(task);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
board.backlog.push(task);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return board;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Move a task to a different board column
|
|
311
|
+
*/
|
|
312
|
+
moveToColumn(id, boardColumn) {
|
|
313
|
+
this.update(id, { boardColumn: boardColumn });
|
|
314
|
+
return this.findById(id);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Set task priority
|
|
318
|
+
*/
|
|
319
|
+
setPriority(id, priority) {
|
|
320
|
+
this.update(id, { priority });
|
|
321
|
+
return this.findById(id);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Set task due date
|
|
325
|
+
*/
|
|
326
|
+
setDueDate(id, dueDate) {
|
|
327
|
+
this.update(id, { dueDate: dueDate || undefined });
|
|
328
|
+
return this.findById(id);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Set task time estimate
|
|
332
|
+
*/
|
|
333
|
+
setEstimate(id, estimatedMinutes) {
|
|
334
|
+
this.update(id, { estimatedMinutes: estimatedMinutes || undefined });
|
|
335
|
+
return this.findById(id);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Add a label to a task
|
|
339
|
+
*/
|
|
340
|
+
addLabel(id, labelId) {
|
|
341
|
+
const task = this.findById(id);
|
|
342
|
+
if (!task)
|
|
343
|
+
return undefined;
|
|
344
|
+
const labels = task.labels || [];
|
|
345
|
+
if (!labels.includes(labelId)) {
|
|
346
|
+
labels.push(labelId);
|
|
347
|
+
this.update(id, { labels });
|
|
348
|
+
}
|
|
349
|
+
return this.findById(id);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Remove a label from a task
|
|
353
|
+
*/
|
|
354
|
+
removeLabel(id, labelId) {
|
|
355
|
+
const task = this.findById(id);
|
|
356
|
+
if (!task)
|
|
357
|
+
return undefined;
|
|
358
|
+
const labels = task.labels || [];
|
|
359
|
+
const newLabels = labels.filter(l => l !== labelId);
|
|
360
|
+
this.update(id, { labels: newLabels });
|
|
361
|
+
return this.findById(id);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Assign an agent role to a task
|
|
365
|
+
*/
|
|
366
|
+
assignAgentRole(id, agentRoleId) {
|
|
367
|
+
this.update(id, { assignedAgentRoleId: agentRoleId || undefined });
|
|
368
|
+
return this.findById(id);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
exports.TaskRepository = TaskRepository;
|
|
372
|
+
// Whitelist of allowed update fields to prevent SQL injection
|
|
373
|
+
TaskRepository.ALLOWED_UPDATE_FIELDS = new Set([
|
|
374
|
+
'title', 'status', 'error', 'result', 'budgetTokens', 'budgetCost',
|
|
375
|
+
'successCriteria', 'maxAttempts', 'currentAttempt', 'completedAt',
|
|
376
|
+
'parentTaskId', 'agentType', 'agentConfig', 'depth', 'resultSummary',
|
|
377
|
+
// Agent Squad fields
|
|
378
|
+
'assignedAgentRoleId', 'boardColumn', 'priority',
|
|
379
|
+
// Task Board fields
|
|
380
|
+
'labels', 'dueDate', 'estimatedMinutes', 'actualMinutes', 'mentionedAgentRoleIds'
|
|
381
|
+
]);
|
|
382
|
+
class TaskEventRepository {
|
|
383
|
+
constructor(db) {
|
|
384
|
+
this.db = db;
|
|
385
|
+
}
|
|
386
|
+
create(event) {
|
|
387
|
+
const newEvent = {
|
|
388
|
+
...event,
|
|
389
|
+
id: (0, uuid_1.v4)(),
|
|
390
|
+
};
|
|
391
|
+
const stmt = this.db.prepare(`
|
|
392
|
+
INSERT INTO task_events (id, task_id, timestamp, type, payload)
|
|
393
|
+
VALUES (?, ?, ?, ?, ?)
|
|
394
|
+
`);
|
|
395
|
+
stmt.run(newEvent.id, newEvent.taskId, newEvent.timestamp, newEvent.type, JSON.stringify(newEvent.payload));
|
|
396
|
+
return newEvent;
|
|
397
|
+
}
|
|
398
|
+
findByTaskId(taskId) {
|
|
399
|
+
const stmt = this.db.prepare(`
|
|
400
|
+
SELECT * FROM task_events
|
|
401
|
+
WHERE task_id = ?
|
|
402
|
+
ORDER BY timestamp ASC
|
|
403
|
+
`);
|
|
404
|
+
const rows = stmt.all(taskId);
|
|
405
|
+
return rows.map(row => this.mapRowToEvent(row));
|
|
406
|
+
}
|
|
407
|
+
mapRowToEvent(row) {
|
|
408
|
+
return {
|
|
409
|
+
id: row.id,
|
|
410
|
+
taskId: row.task_id,
|
|
411
|
+
timestamp: row.timestamp,
|
|
412
|
+
type: row.type,
|
|
413
|
+
payload: safeJsonParse(row.payload, {}, 'taskEvent.payload'),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Prune old conversation snapshots for a task, keeping only the most recent one.
|
|
418
|
+
* This prevents database bloat from accumulating snapshots over time.
|
|
419
|
+
*/
|
|
420
|
+
pruneOldSnapshots(taskId) {
|
|
421
|
+
// Find all conversation_snapshot events for this task, ordered by timestamp descending
|
|
422
|
+
const findStmt = this.db.prepare(`
|
|
423
|
+
SELECT id, timestamp FROM task_events
|
|
424
|
+
WHERE task_id = ? AND type = 'conversation_snapshot'
|
|
425
|
+
ORDER BY timestamp DESC
|
|
426
|
+
`);
|
|
427
|
+
const snapshots = findStmt.all(taskId);
|
|
428
|
+
// Keep only the most recent one, delete the rest
|
|
429
|
+
if (snapshots.length > 1) {
|
|
430
|
+
const idsToDelete = snapshots.slice(1).map(s => s.id);
|
|
431
|
+
const deleteStmt = this.db.prepare(`
|
|
432
|
+
DELETE FROM task_events WHERE id = ?
|
|
433
|
+
`);
|
|
434
|
+
for (const id of idsToDelete) {
|
|
435
|
+
deleteStmt.run(id);
|
|
436
|
+
}
|
|
437
|
+
console.log(`[TaskEventRepository] Pruned ${idsToDelete.length} old snapshot(s) for task ${taskId}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
exports.TaskEventRepository = TaskEventRepository;
|
|
442
|
+
class ArtifactRepository {
|
|
443
|
+
constructor(db) {
|
|
444
|
+
this.db = db;
|
|
445
|
+
}
|
|
446
|
+
create(artifact) {
|
|
447
|
+
const newArtifact = {
|
|
448
|
+
...artifact,
|
|
449
|
+
id: (0, uuid_1.v4)(),
|
|
450
|
+
};
|
|
451
|
+
const stmt = this.db.prepare(`
|
|
452
|
+
INSERT INTO artifacts (id, task_id, path, mime_type, sha256, size, created_at)
|
|
453
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
454
|
+
`);
|
|
455
|
+
stmt.run(newArtifact.id, newArtifact.taskId, newArtifact.path, newArtifact.mimeType, newArtifact.sha256, newArtifact.size, newArtifact.createdAt);
|
|
456
|
+
return newArtifact;
|
|
457
|
+
}
|
|
458
|
+
findByTaskId(taskId) {
|
|
459
|
+
const stmt = this.db.prepare('SELECT * FROM artifacts WHERE task_id = ? ORDER BY created_at DESC');
|
|
460
|
+
const rows = stmt.all(taskId);
|
|
461
|
+
return rows.map(row => this.mapRowToArtifact(row));
|
|
462
|
+
}
|
|
463
|
+
mapRowToArtifact(row) {
|
|
464
|
+
return {
|
|
465
|
+
id: row.id,
|
|
466
|
+
taskId: row.task_id,
|
|
467
|
+
path: row.path,
|
|
468
|
+
mimeType: row.mime_type,
|
|
469
|
+
sha256: row.sha256,
|
|
470
|
+
size: row.size,
|
|
471
|
+
createdAt: row.created_at,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
exports.ArtifactRepository = ArtifactRepository;
|
|
476
|
+
class ApprovalRepository {
|
|
477
|
+
constructor(db) {
|
|
478
|
+
this.db = db;
|
|
479
|
+
}
|
|
480
|
+
create(approval) {
|
|
481
|
+
const newApproval = {
|
|
482
|
+
...approval,
|
|
483
|
+
id: (0, uuid_1.v4)(),
|
|
484
|
+
};
|
|
485
|
+
const stmt = this.db.prepare(`
|
|
486
|
+
INSERT INTO approvals (id, task_id, type, description, details, status, requested_at)
|
|
487
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
488
|
+
`);
|
|
489
|
+
stmt.run(newApproval.id, newApproval.taskId, newApproval.type, newApproval.description, JSON.stringify(newApproval.details), newApproval.status, newApproval.requestedAt);
|
|
490
|
+
return newApproval;
|
|
491
|
+
}
|
|
492
|
+
update(id, status) {
|
|
493
|
+
const stmt = this.db.prepare(`
|
|
494
|
+
UPDATE approvals
|
|
495
|
+
SET status = ?, resolved_at = ?
|
|
496
|
+
WHERE id = ?
|
|
497
|
+
`);
|
|
498
|
+
stmt.run(status, Date.now(), id);
|
|
499
|
+
}
|
|
500
|
+
findPendingByTaskId(taskId) {
|
|
501
|
+
const stmt = this.db.prepare(`
|
|
502
|
+
SELECT * FROM approvals
|
|
503
|
+
WHERE task_id = ? AND status = 'pending'
|
|
504
|
+
ORDER BY requested_at ASC
|
|
505
|
+
`);
|
|
506
|
+
const rows = stmt.all(taskId);
|
|
507
|
+
return rows.map(row => this.mapRowToApproval(row));
|
|
508
|
+
}
|
|
509
|
+
mapRowToApproval(row) {
|
|
510
|
+
return {
|
|
511
|
+
id: row.id,
|
|
512
|
+
taskId: row.task_id,
|
|
513
|
+
type: row.type,
|
|
514
|
+
description: row.description,
|
|
515
|
+
details: safeJsonParse(row.details, {}, 'approval.details'),
|
|
516
|
+
status: row.status,
|
|
517
|
+
requestedAt: row.requested_at,
|
|
518
|
+
resolvedAt: row.resolved_at || undefined,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
exports.ApprovalRepository = ApprovalRepository;
|
|
523
|
+
class SkillRepository {
|
|
524
|
+
constructor(db) {
|
|
525
|
+
this.db = db;
|
|
526
|
+
}
|
|
527
|
+
create(skill) {
|
|
528
|
+
const newSkill = {
|
|
529
|
+
...skill,
|
|
530
|
+
id: (0, uuid_1.v4)(),
|
|
531
|
+
};
|
|
532
|
+
const stmt = this.db.prepare(`
|
|
533
|
+
INSERT INTO skills (id, name, description, category, prompt, script_path, parameters)
|
|
534
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
535
|
+
`);
|
|
536
|
+
stmt.run(newSkill.id, newSkill.name, newSkill.description, newSkill.category, newSkill.prompt, newSkill.scriptPath || null, newSkill.parameters ? JSON.stringify(newSkill.parameters) : null);
|
|
537
|
+
return newSkill;
|
|
538
|
+
}
|
|
539
|
+
findAll() {
|
|
540
|
+
const stmt = this.db.prepare('SELECT * FROM skills ORDER BY name ASC');
|
|
541
|
+
const rows = stmt.all();
|
|
542
|
+
return rows.map(row => this.mapRowToSkill(row));
|
|
543
|
+
}
|
|
544
|
+
findById(id) {
|
|
545
|
+
const stmt = this.db.prepare('SELECT * FROM skills WHERE id = ?');
|
|
546
|
+
const row = stmt.get(id);
|
|
547
|
+
return row ? this.mapRowToSkill(row) : undefined;
|
|
548
|
+
}
|
|
549
|
+
mapRowToSkill(row) {
|
|
550
|
+
return {
|
|
551
|
+
id: row.id,
|
|
552
|
+
name: row.name,
|
|
553
|
+
description: row.description,
|
|
554
|
+
category: row.category,
|
|
555
|
+
prompt: row.prompt,
|
|
556
|
+
scriptPath: row.script_path || undefined,
|
|
557
|
+
parameters: row.parameters ? safeJsonParse(row.parameters, undefined, 'skill.parameters') : undefined,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
exports.SkillRepository = SkillRepository;
|
|
562
|
+
class LLMModelRepository {
|
|
563
|
+
constructor(db) {
|
|
564
|
+
this.db = db;
|
|
565
|
+
}
|
|
566
|
+
findAll() {
|
|
567
|
+
const stmt = this.db.prepare(`
|
|
568
|
+
SELECT * FROM llm_models
|
|
569
|
+
WHERE is_active = 1
|
|
570
|
+
ORDER BY sort_order ASC
|
|
571
|
+
`);
|
|
572
|
+
const rows = stmt.all();
|
|
573
|
+
return rows.map(row => this.mapRowToModel(row));
|
|
574
|
+
}
|
|
575
|
+
findByKey(key) {
|
|
576
|
+
const stmt = this.db.prepare('SELECT * FROM llm_models WHERE key = ?');
|
|
577
|
+
const row = stmt.get(key);
|
|
578
|
+
return row ? this.mapRowToModel(row) : undefined;
|
|
579
|
+
}
|
|
580
|
+
findById(id) {
|
|
581
|
+
const stmt = this.db.prepare('SELECT * FROM llm_models WHERE id = ?');
|
|
582
|
+
const row = stmt.get(id);
|
|
583
|
+
return row ? this.mapRowToModel(row) : undefined;
|
|
584
|
+
}
|
|
585
|
+
mapRowToModel(row) {
|
|
586
|
+
return {
|
|
587
|
+
id: row.id,
|
|
588
|
+
key: row.key,
|
|
589
|
+
displayName: row.display_name,
|
|
590
|
+
description: row.description,
|
|
591
|
+
anthropicModelId: row.anthropic_model_id,
|
|
592
|
+
bedrockModelId: row.bedrock_model_id,
|
|
593
|
+
sortOrder: row.sort_order,
|
|
594
|
+
isActive: row.is_active === 1,
|
|
595
|
+
createdAt: row.created_at,
|
|
596
|
+
updatedAt: row.updated_at,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
exports.LLMModelRepository = LLMModelRepository;
|
|
601
|
+
class ChannelRepository {
|
|
602
|
+
constructor(db) {
|
|
603
|
+
this.db = db;
|
|
604
|
+
}
|
|
605
|
+
create(channel) {
|
|
606
|
+
const now = Date.now();
|
|
607
|
+
const newChannel = {
|
|
608
|
+
...channel,
|
|
609
|
+
id: (0, uuid_1.v4)(),
|
|
610
|
+
createdAt: now,
|
|
611
|
+
updatedAt: now,
|
|
612
|
+
};
|
|
613
|
+
const stmt = this.db.prepare(`
|
|
614
|
+
INSERT INTO channels (id, type, name, enabled, config, security_config, status, bot_username, created_at, updated_at)
|
|
615
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
616
|
+
`);
|
|
617
|
+
stmt.run(newChannel.id, newChannel.type, newChannel.name, newChannel.enabled ? 1 : 0, JSON.stringify(newChannel.config), JSON.stringify(newChannel.securityConfig), newChannel.status, newChannel.botUsername || null, newChannel.createdAt, newChannel.updatedAt);
|
|
618
|
+
return newChannel;
|
|
619
|
+
}
|
|
620
|
+
update(id, updates) {
|
|
621
|
+
const fields = [];
|
|
622
|
+
const values = [];
|
|
623
|
+
if (updates.name !== undefined) {
|
|
624
|
+
fields.push('name = ?');
|
|
625
|
+
values.push(updates.name);
|
|
626
|
+
}
|
|
627
|
+
if (updates.enabled !== undefined) {
|
|
628
|
+
fields.push('enabled = ?');
|
|
629
|
+
values.push(updates.enabled ? 1 : 0);
|
|
630
|
+
}
|
|
631
|
+
if (updates.config !== undefined) {
|
|
632
|
+
fields.push('config = ?');
|
|
633
|
+
values.push(JSON.stringify(updates.config));
|
|
634
|
+
}
|
|
635
|
+
if (updates.securityConfig !== undefined) {
|
|
636
|
+
fields.push('security_config = ?');
|
|
637
|
+
values.push(JSON.stringify(updates.securityConfig));
|
|
638
|
+
}
|
|
639
|
+
if (updates.status !== undefined) {
|
|
640
|
+
fields.push('status = ?');
|
|
641
|
+
values.push(updates.status);
|
|
642
|
+
}
|
|
643
|
+
if (updates.botUsername !== undefined) {
|
|
644
|
+
fields.push('bot_username = ?');
|
|
645
|
+
values.push(updates.botUsername);
|
|
646
|
+
}
|
|
647
|
+
if (fields.length === 0)
|
|
648
|
+
return;
|
|
649
|
+
fields.push('updated_at = ?');
|
|
650
|
+
values.push(Date.now());
|
|
651
|
+
values.push(id);
|
|
652
|
+
const stmt = this.db.prepare(`UPDATE channels SET ${fields.join(', ')} WHERE id = ?`);
|
|
653
|
+
stmt.run(...values);
|
|
654
|
+
}
|
|
655
|
+
findById(id) {
|
|
656
|
+
const stmt = this.db.prepare('SELECT * FROM channels WHERE id = ?');
|
|
657
|
+
const row = stmt.get(id);
|
|
658
|
+
return row ? this.mapRowToChannel(row) : undefined;
|
|
659
|
+
}
|
|
660
|
+
findByType(type) {
|
|
661
|
+
const stmt = this.db.prepare('SELECT * FROM channels WHERE type = ?');
|
|
662
|
+
const row = stmt.get(type);
|
|
663
|
+
return row ? this.mapRowToChannel(row) : undefined;
|
|
664
|
+
}
|
|
665
|
+
findAll() {
|
|
666
|
+
const stmt = this.db.prepare('SELECT * FROM channels ORDER BY created_at ASC');
|
|
667
|
+
const rows = stmt.all();
|
|
668
|
+
return rows.map(row => this.mapRowToChannel(row));
|
|
669
|
+
}
|
|
670
|
+
findEnabled() {
|
|
671
|
+
const stmt = this.db.prepare('SELECT * FROM channels WHERE enabled = 1 ORDER BY created_at ASC');
|
|
672
|
+
const rows = stmt.all();
|
|
673
|
+
return rows.map(row => this.mapRowToChannel(row));
|
|
674
|
+
}
|
|
675
|
+
delete(id) {
|
|
676
|
+
const stmt = this.db.prepare('DELETE FROM channels WHERE id = ?');
|
|
677
|
+
stmt.run(id);
|
|
678
|
+
}
|
|
679
|
+
mapRowToChannel(row) {
|
|
680
|
+
const defaultSecurityConfig = { mode: 'pairing' };
|
|
681
|
+
return {
|
|
682
|
+
id: row.id,
|
|
683
|
+
type: row.type,
|
|
684
|
+
name: row.name,
|
|
685
|
+
enabled: row.enabled === 1,
|
|
686
|
+
config: safeJsonParse(row.config, {}, 'channel.config'),
|
|
687
|
+
securityConfig: safeJsonParse(row.security_config, defaultSecurityConfig, 'channel.securityConfig'),
|
|
688
|
+
status: row.status,
|
|
689
|
+
botUsername: row.bot_username || undefined,
|
|
690
|
+
createdAt: row.created_at,
|
|
691
|
+
updatedAt: row.updated_at,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
exports.ChannelRepository = ChannelRepository;
|
|
696
|
+
class ChannelUserRepository {
|
|
697
|
+
constructor(db) {
|
|
698
|
+
this.db = db;
|
|
699
|
+
}
|
|
700
|
+
create(user) {
|
|
701
|
+
const now = Date.now();
|
|
702
|
+
const newUser = {
|
|
703
|
+
...user,
|
|
704
|
+
id: (0, uuid_1.v4)(),
|
|
705
|
+
pairingAttempts: 0,
|
|
706
|
+
createdAt: now,
|
|
707
|
+
lastSeenAt: now,
|
|
708
|
+
};
|
|
709
|
+
const stmt = this.db.prepare(`
|
|
710
|
+
INSERT INTO channel_users (id, channel_id, channel_user_id, display_name, username, allowed, pairing_code, pairing_attempts, pairing_expires_at, created_at, last_seen_at)
|
|
711
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
712
|
+
`);
|
|
713
|
+
stmt.run(newUser.id, newUser.channelId, newUser.channelUserId, newUser.displayName, newUser.username || null, newUser.allowed ? 1 : 0, newUser.pairingCode || null, newUser.pairingAttempts, newUser.pairingExpiresAt || null, newUser.createdAt, newUser.lastSeenAt);
|
|
714
|
+
return newUser;
|
|
715
|
+
}
|
|
716
|
+
update(id, updates) {
|
|
717
|
+
const fields = [];
|
|
718
|
+
const values = [];
|
|
719
|
+
if (updates.displayName !== undefined) {
|
|
720
|
+
fields.push('display_name = ?');
|
|
721
|
+
values.push(updates.displayName);
|
|
722
|
+
}
|
|
723
|
+
if (updates.username !== undefined) {
|
|
724
|
+
fields.push('username = ?');
|
|
725
|
+
values.push(updates.username);
|
|
726
|
+
}
|
|
727
|
+
if (updates.allowed !== undefined) {
|
|
728
|
+
fields.push('allowed = ?');
|
|
729
|
+
values.push(updates.allowed ? 1 : 0);
|
|
730
|
+
}
|
|
731
|
+
if (updates.pairingCode !== undefined) {
|
|
732
|
+
fields.push('pairing_code = ?');
|
|
733
|
+
values.push(updates.pairingCode);
|
|
734
|
+
}
|
|
735
|
+
if (updates.pairingAttempts !== undefined) {
|
|
736
|
+
fields.push('pairing_attempts = ?');
|
|
737
|
+
values.push(updates.pairingAttempts);
|
|
738
|
+
}
|
|
739
|
+
if (updates.pairingExpiresAt !== undefined) {
|
|
740
|
+
fields.push('pairing_expires_at = ?');
|
|
741
|
+
values.push(updates.pairingExpiresAt);
|
|
742
|
+
}
|
|
743
|
+
if (updates.lockoutUntil !== undefined) {
|
|
744
|
+
fields.push('lockout_until = ?');
|
|
745
|
+
values.push(updates.lockoutUntil);
|
|
746
|
+
}
|
|
747
|
+
if (updates.lastSeenAt !== undefined) {
|
|
748
|
+
fields.push('last_seen_at = ?');
|
|
749
|
+
values.push(updates.lastSeenAt);
|
|
750
|
+
}
|
|
751
|
+
if (fields.length === 0)
|
|
752
|
+
return;
|
|
753
|
+
values.push(id);
|
|
754
|
+
const stmt = this.db.prepare(`UPDATE channel_users SET ${fields.join(', ')} WHERE id = ?`);
|
|
755
|
+
stmt.run(...values);
|
|
756
|
+
}
|
|
757
|
+
findById(id) {
|
|
758
|
+
const stmt = this.db.prepare('SELECT * FROM channel_users WHERE id = ?');
|
|
759
|
+
const row = stmt.get(id);
|
|
760
|
+
return row ? this.mapRowToUser(row) : undefined;
|
|
761
|
+
}
|
|
762
|
+
findByChannelUserId(channelId, channelUserId) {
|
|
763
|
+
const stmt = this.db.prepare('SELECT * FROM channel_users WHERE channel_id = ? AND channel_user_id = ?');
|
|
764
|
+
const row = stmt.get(channelId, channelUserId);
|
|
765
|
+
return row ? this.mapRowToUser(row) : undefined;
|
|
766
|
+
}
|
|
767
|
+
findByChannelId(channelId) {
|
|
768
|
+
const stmt = this.db.prepare('SELECT * FROM channel_users WHERE channel_id = ? ORDER BY last_seen_at DESC');
|
|
769
|
+
const rows = stmt.all(channelId);
|
|
770
|
+
return rows.map(row => this.mapRowToUser(row));
|
|
771
|
+
}
|
|
772
|
+
findAllowedByChannelId(channelId) {
|
|
773
|
+
const stmt = this.db.prepare('SELECT * FROM channel_users WHERE channel_id = ? AND allowed = 1 ORDER BY last_seen_at DESC');
|
|
774
|
+
const rows = stmt.all(channelId);
|
|
775
|
+
return rows.map(row => this.mapRowToUser(row));
|
|
776
|
+
}
|
|
777
|
+
deleteByChannelId(channelId) {
|
|
778
|
+
const stmt = this.db.prepare('DELETE FROM channel_users WHERE channel_id = ?');
|
|
779
|
+
stmt.run(channelId);
|
|
780
|
+
}
|
|
781
|
+
delete(id) {
|
|
782
|
+
const stmt = this.db.prepare('DELETE FROM channel_users WHERE id = ?');
|
|
783
|
+
stmt.run(id);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Delete expired pending pairing entries
|
|
787
|
+
* These are placeholder entries created when generating pairing codes that have expired
|
|
788
|
+
* Returns the number of deleted entries
|
|
789
|
+
*/
|
|
790
|
+
deleteExpiredPending(channelId) {
|
|
791
|
+
const now = Date.now();
|
|
792
|
+
const stmt = this.db.prepare(`
|
|
793
|
+
DELETE FROM channel_users
|
|
794
|
+
WHERE channel_id = ?
|
|
795
|
+
AND allowed = 0
|
|
796
|
+
AND channel_user_id LIKE 'pending_%'
|
|
797
|
+
AND pairing_expires_at IS NOT NULL
|
|
798
|
+
AND pairing_expires_at < ?
|
|
799
|
+
`);
|
|
800
|
+
const result = stmt.run(channelId, now);
|
|
801
|
+
return result.changes;
|
|
802
|
+
}
|
|
803
|
+
findByPairingCode(channelId, pairingCode) {
|
|
804
|
+
const stmt = this.db.prepare('SELECT * FROM channel_users WHERE channel_id = ? AND UPPER(pairing_code) = UPPER(?)');
|
|
805
|
+
const row = stmt.get(channelId, pairingCode);
|
|
806
|
+
return row ? this.mapRowToUser(row) : undefined;
|
|
807
|
+
}
|
|
808
|
+
mapRowToUser(row) {
|
|
809
|
+
return {
|
|
810
|
+
id: row.id,
|
|
811
|
+
channelId: row.channel_id,
|
|
812
|
+
channelUserId: row.channel_user_id,
|
|
813
|
+
displayName: row.display_name,
|
|
814
|
+
username: row.username || undefined,
|
|
815
|
+
allowed: row.allowed === 1,
|
|
816
|
+
pairingCode: row.pairing_code || undefined,
|
|
817
|
+
pairingAttempts: row.pairing_attempts,
|
|
818
|
+
pairingExpiresAt: row.pairing_expires_at || undefined,
|
|
819
|
+
lockoutUntil: row.lockout_until || undefined,
|
|
820
|
+
createdAt: row.created_at,
|
|
821
|
+
lastSeenAt: row.last_seen_at,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
exports.ChannelUserRepository = ChannelUserRepository;
|
|
826
|
+
class ChannelSessionRepository {
|
|
827
|
+
constructor(db) {
|
|
828
|
+
this.db = db;
|
|
829
|
+
}
|
|
830
|
+
create(session) {
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const newSession = {
|
|
833
|
+
...session,
|
|
834
|
+
id: (0, uuid_1.v4)(),
|
|
835
|
+
createdAt: now,
|
|
836
|
+
lastActivityAt: now,
|
|
837
|
+
};
|
|
838
|
+
const stmt = this.db.prepare(`
|
|
839
|
+
INSERT INTO channel_sessions (id, channel_id, chat_id, user_id, task_id, workspace_id, state, context, created_at, last_activity_at)
|
|
840
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
841
|
+
`);
|
|
842
|
+
stmt.run(newSession.id, newSession.channelId, newSession.chatId, newSession.userId || null, newSession.taskId || null, newSession.workspaceId || null, newSession.state, newSession.context ? JSON.stringify(newSession.context) : null, newSession.createdAt, newSession.lastActivityAt);
|
|
843
|
+
return newSession;
|
|
844
|
+
}
|
|
845
|
+
update(id, updates) {
|
|
846
|
+
const fields = [];
|
|
847
|
+
const values = [];
|
|
848
|
+
// Use 'in' check to allow setting fields to null/undefined (clearing them)
|
|
849
|
+
if ('taskId' in updates) {
|
|
850
|
+
fields.push('task_id = ?');
|
|
851
|
+
values.push(updates.taskId ?? null); // Convert undefined to null for SQLite
|
|
852
|
+
}
|
|
853
|
+
if ('workspaceId' in updates) {
|
|
854
|
+
fields.push('workspace_id = ?');
|
|
855
|
+
values.push(updates.workspaceId ?? null);
|
|
856
|
+
}
|
|
857
|
+
if ('state' in updates) {
|
|
858
|
+
fields.push('state = ?');
|
|
859
|
+
values.push(updates.state);
|
|
860
|
+
}
|
|
861
|
+
if ('lastActivityAt' in updates) {
|
|
862
|
+
fields.push('last_activity_at = ?');
|
|
863
|
+
values.push(updates.lastActivityAt);
|
|
864
|
+
}
|
|
865
|
+
// Handle shellEnabled and debugMode by merging into context
|
|
866
|
+
const hasContextUpdate = 'context' in updates || 'shellEnabled' in updates || 'debugMode' in updates;
|
|
867
|
+
if (hasContextUpdate) {
|
|
868
|
+
// Load existing session to merge context
|
|
869
|
+
const existing = this.findById(id);
|
|
870
|
+
const existingContext = existing?.context || {};
|
|
871
|
+
const newContext = {
|
|
872
|
+
...existingContext,
|
|
873
|
+
...('context' in updates ? updates.context : {}),
|
|
874
|
+
...('shellEnabled' in updates ? { shellEnabled: updates.shellEnabled } : {}),
|
|
875
|
+
...('debugMode' in updates ? { debugMode: updates.debugMode } : {}),
|
|
876
|
+
};
|
|
877
|
+
fields.push('context = ?');
|
|
878
|
+
values.push(JSON.stringify(newContext));
|
|
879
|
+
}
|
|
880
|
+
if (fields.length === 0)
|
|
881
|
+
return;
|
|
882
|
+
values.push(id);
|
|
883
|
+
const stmt = this.db.prepare(`UPDATE channel_sessions SET ${fields.join(', ')} WHERE id = ?`);
|
|
884
|
+
stmt.run(...values);
|
|
885
|
+
}
|
|
886
|
+
findById(id) {
|
|
887
|
+
const stmt = this.db.prepare('SELECT * FROM channel_sessions WHERE id = ?');
|
|
888
|
+
const row = stmt.get(id);
|
|
889
|
+
return row ? this.mapRowToSession(row) : undefined;
|
|
890
|
+
}
|
|
891
|
+
findByChatId(channelId, chatId) {
|
|
892
|
+
const stmt = this.db.prepare('SELECT * FROM channel_sessions WHERE channel_id = ? AND chat_id = ? ORDER BY last_activity_at DESC LIMIT 1');
|
|
893
|
+
const row = stmt.get(channelId, chatId);
|
|
894
|
+
return row ? this.mapRowToSession(row) : undefined;
|
|
895
|
+
}
|
|
896
|
+
findByTaskId(taskId) {
|
|
897
|
+
const stmt = this.db.prepare('SELECT * FROM channel_sessions WHERE task_id = ?');
|
|
898
|
+
const row = stmt.get(taskId);
|
|
899
|
+
return row ? this.mapRowToSession(row) : undefined;
|
|
900
|
+
}
|
|
901
|
+
findActiveByChannelId(channelId) {
|
|
902
|
+
const stmt = this.db.prepare("SELECT * FROM channel_sessions WHERE channel_id = ? AND state != 'idle' ORDER BY last_activity_at DESC");
|
|
903
|
+
const rows = stmt.all(channelId);
|
|
904
|
+
return rows.map(row => this.mapRowToSession(row));
|
|
905
|
+
}
|
|
906
|
+
deleteByChannelId(channelId) {
|
|
907
|
+
const stmt = this.db.prepare('DELETE FROM channel_sessions WHERE channel_id = ?');
|
|
908
|
+
stmt.run(channelId);
|
|
909
|
+
}
|
|
910
|
+
mapRowToSession(row) {
|
|
911
|
+
const context = row.context ? safeJsonParse(row.context, {}, 'session.context') : undefined;
|
|
912
|
+
// Extract shellEnabled and debugMode from context
|
|
913
|
+
const shellEnabled = context?.shellEnabled;
|
|
914
|
+
const debugMode = context?.debugMode;
|
|
915
|
+
return {
|
|
916
|
+
id: row.id,
|
|
917
|
+
channelId: row.channel_id,
|
|
918
|
+
chatId: row.chat_id,
|
|
919
|
+
userId: row.user_id || undefined,
|
|
920
|
+
taskId: row.task_id || undefined,
|
|
921
|
+
workspaceId: row.workspace_id || undefined,
|
|
922
|
+
state: row.state,
|
|
923
|
+
context,
|
|
924
|
+
shellEnabled,
|
|
925
|
+
debugMode,
|
|
926
|
+
createdAt: row.created_at,
|
|
927
|
+
lastActivityAt: row.last_activity_at,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
exports.ChannelSessionRepository = ChannelSessionRepository;
|
|
932
|
+
class ChannelMessageRepository {
|
|
933
|
+
constructor(db) {
|
|
934
|
+
this.db = db;
|
|
935
|
+
}
|
|
936
|
+
create(message) {
|
|
937
|
+
const newMessage = {
|
|
938
|
+
...message,
|
|
939
|
+
id: (0, uuid_1.v4)(),
|
|
940
|
+
};
|
|
941
|
+
const stmt = this.db.prepare(`
|
|
942
|
+
INSERT INTO channel_messages (id, channel_id, session_id, channel_message_id, chat_id, user_id, direction, content, attachments, timestamp)
|
|
943
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
944
|
+
`);
|
|
945
|
+
stmt.run(newMessage.id, newMessage.channelId, newMessage.sessionId || null, newMessage.channelMessageId, newMessage.chatId, newMessage.userId || null, newMessage.direction, newMessage.content, newMessage.attachments ? JSON.stringify(newMessage.attachments) : null, newMessage.timestamp);
|
|
946
|
+
return newMessage;
|
|
947
|
+
}
|
|
948
|
+
findBySessionId(sessionId, limit = 50) {
|
|
949
|
+
const stmt = this.db.prepare('SELECT * FROM channel_messages WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?');
|
|
950
|
+
const rows = stmt.all(sessionId, limit);
|
|
951
|
+
return rows.map(row => this.mapRowToMessage(row)).reverse();
|
|
952
|
+
}
|
|
953
|
+
findByChatId(channelId, chatId, limit = 50) {
|
|
954
|
+
const stmt = this.db.prepare('SELECT * FROM channel_messages WHERE channel_id = ? AND chat_id = ? ORDER BY timestamp DESC LIMIT ?');
|
|
955
|
+
const rows = stmt.all(channelId, chatId, limit);
|
|
956
|
+
return rows.map(row => this.mapRowToMessage(row)).reverse();
|
|
957
|
+
}
|
|
958
|
+
deleteByChannelId(channelId) {
|
|
959
|
+
const stmt = this.db.prepare('DELETE FROM channel_messages WHERE channel_id = ?');
|
|
960
|
+
stmt.run(channelId);
|
|
961
|
+
}
|
|
962
|
+
mapRowToMessage(row) {
|
|
963
|
+
return {
|
|
964
|
+
id: row.id,
|
|
965
|
+
channelId: row.channel_id,
|
|
966
|
+
sessionId: row.session_id || undefined,
|
|
967
|
+
channelMessageId: row.channel_message_id,
|
|
968
|
+
chatId: row.chat_id,
|
|
969
|
+
userId: row.user_id || undefined,
|
|
970
|
+
direction: row.direction,
|
|
971
|
+
content: row.content,
|
|
972
|
+
attachments: row.attachments ? safeJsonParse(row.attachments, undefined, 'message.attachments') : undefined,
|
|
973
|
+
timestamp: row.timestamp,
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
exports.ChannelMessageRepository = ChannelMessageRepository;
|
|
978
|
+
class MessageQueueRepository {
|
|
979
|
+
constructor(db) {
|
|
980
|
+
this.db = db;
|
|
981
|
+
}
|
|
982
|
+
enqueue(item) {
|
|
983
|
+
const newItem = {
|
|
984
|
+
...item,
|
|
985
|
+
id: (0, uuid_1.v4)(),
|
|
986
|
+
status: 'pending',
|
|
987
|
+
attempts: 0,
|
|
988
|
+
createdAt: Date.now(),
|
|
989
|
+
};
|
|
990
|
+
const stmt = this.db.prepare(`
|
|
991
|
+
INSERT INTO message_queue (id, channel_type, chat_id, message, priority, status, attempts, max_attempts, last_attempt_at, error, created_at, scheduled_at)
|
|
992
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
993
|
+
`);
|
|
994
|
+
stmt.run(newItem.id, newItem.channelType, newItem.chatId, JSON.stringify(newItem.message), newItem.priority, newItem.status, newItem.attempts, newItem.maxAttempts, newItem.lastAttemptAt || null, newItem.error || null, newItem.createdAt, newItem.scheduledAt || null);
|
|
995
|
+
return newItem;
|
|
996
|
+
}
|
|
997
|
+
update(id, updates) {
|
|
998
|
+
const fields = [];
|
|
999
|
+
const values = [];
|
|
1000
|
+
if (updates.status !== undefined) {
|
|
1001
|
+
fields.push('status = ?');
|
|
1002
|
+
values.push(updates.status);
|
|
1003
|
+
}
|
|
1004
|
+
if (updates.attempts !== undefined) {
|
|
1005
|
+
fields.push('attempts = ?');
|
|
1006
|
+
values.push(updates.attempts);
|
|
1007
|
+
}
|
|
1008
|
+
if (updates.lastAttemptAt !== undefined) {
|
|
1009
|
+
fields.push('last_attempt_at = ?');
|
|
1010
|
+
values.push(updates.lastAttemptAt);
|
|
1011
|
+
}
|
|
1012
|
+
if (updates.error !== undefined) {
|
|
1013
|
+
fields.push('error = ?');
|
|
1014
|
+
values.push(updates.error);
|
|
1015
|
+
}
|
|
1016
|
+
if (fields.length === 0)
|
|
1017
|
+
return;
|
|
1018
|
+
values.push(id);
|
|
1019
|
+
const stmt = this.db.prepare(`UPDATE message_queue SET ${fields.join(', ')} WHERE id = ?`);
|
|
1020
|
+
stmt.run(...values);
|
|
1021
|
+
}
|
|
1022
|
+
findPending(limit = 50) {
|
|
1023
|
+
const now = Date.now();
|
|
1024
|
+
const stmt = this.db.prepare(`
|
|
1025
|
+
SELECT * FROM message_queue
|
|
1026
|
+
WHERE status = 'pending' AND (scheduled_at IS NULL OR scheduled_at <= ?)
|
|
1027
|
+
ORDER BY priority DESC, created_at ASC
|
|
1028
|
+
LIMIT ?
|
|
1029
|
+
`);
|
|
1030
|
+
const rows = stmt.all(now, limit);
|
|
1031
|
+
return rows.map(row => this.mapRowToItem(row));
|
|
1032
|
+
}
|
|
1033
|
+
findById(id) {
|
|
1034
|
+
const stmt = this.db.prepare('SELECT * FROM message_queue WHERE id = ?');
|
|
1035
|
+
const row = stmt.get(id);
|
|
1036
|
+
return row ? this.mapRowToItem(row) : undefined;
|
|
1037
|
+
}
|
|
1038
|
+
delete(id) {
|
|
1039
|
+
const stmt = this.db.prepare('DELETE FROM message_queue WHERE id = ?');
|
|
1040
|
+
stmt.run(id);
|
|
1041
|
+
}
|
|
1042
|
+
deleteOld(olderThanMs) {
|
|
1043
|
+
const cutoff = Date.now() - olderThanMs;
|
|
1044
|
+
const stmt = this.db.prepare("DELETE FROM message_queue WHERE status IN ('sent', 'failed') AND created_at < ?");
|
|
1045
|
+
const result = stmt.run(cutoff);
|
|
1046
|
+
return result.changes;
|
|
1047
|
+
}
|
|
1048
|
+
mapRowToItem(row) {
|
|
1049
|
+
return {
|
|
1050
|
+
id: row.id,
|
|
1051
|
+
channelType: row.channel_type,
|
|
1052
|
+
chatId: row.chat_id,
|
|
1053
|
+
message: safeJsonParse(row.message, {}, 'queue.message'),
|
|
1054
|
+
priority: row.priority,
|
|
1055
|
+
status: row.status,
|
|
1056
|
+
attempts: row.attempts,
|
|
1057
|
+
maxAttempts: row.max_attempts,
|
|
1058
|
+
lastAttemptAt: row.last_attempt_at || undefined,
|
|
1059
|
+
error: row.error || undefined,
|
|
1060
|
+
createdAt: row.created_at,
|
|
1061
|
+
scheduledAt: row.scheduled_at || undefined,
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
exports.MessageQueueRepository = MessageQueueRepository;
|
|
1066
|
+
class ScheduledMessageRepository {
|
|
1067
|
+
constructor(db) {
|
|
1068
|
+
this.db = db;
|
|
1069
|
+
}
|
|
1070
|
+
create(item) {
|
|
1071
|
+
const newItem = {
|
|
1072
|
+
...item,
|
|
1073
|
+
id: (0, uuid_1.v4)(),
|
|
1074
|
+
status: 'pending',
|
|
1075
|
+
createdAt: Date.now(),
|
|
1076
|
+
};
|
|
1077
|
+
const stmt = this.db.prepare(`
|
|
1078
|
+
INSERT INTO scheduled_messages (id, channel_type, chat_id, message, scheduled_at, status, sent_message_id, error, created_at)
|
|
1079
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1080
|
+
`);
|
|
1081
|
+
stmt.run(newItem.id, newItem.channelType, newItem.chatId, JSON.stringify(newItem.message), newItem.scheduledAt, newItem.status, newItem.sentMessageId || null, newItem.error || null, newItem.createdAt);
|
|
1082
|
+
return newItem;
|
|
1083
|
+
}
|
|
1084
|
+
update(id, updates) {
|
|
1085
|
+
const fields = [];
|
|
1086
|
+
const values = [];
|
|
1087
|
+
if (updates.status !== undefined) {
|
|
1088
|
+
fields.push('status = ?');
|
|
1089
|
+
values.push(updates.status);
|
|
1090
|
+
}
|
|
1091
|
+
if (updates.sentMessageId !== undefined) {
|
|
1092
|
+
fields.push('sent_message_id = ?');
|
|
1093
|
+
values.push(updates.sentMessageId);
|
|
1094
|
+
}
|
|
1095
|
+
if (updates.error !== undefined) {
|
|
1096
|
+
fields.push('error = ?');
|
|
1097
|
+
values.push(updates.error);
|
|
1098
|
+
}
|
|
1099
|
+
if (updates.scheduledAt !== undefined) {
|
|
1100
|
+
fields.push('scheduled_at = ?');
|
|
1101
|
+
values.push(updates.scheduledAt);
|
|
1102
|
+
}
|
|
1103
|
+
if (fields.length === 0)
|
|
1104
|
+
return;
|
|
1105
|
+
values.push(id);
|
|
1106
|
+
const stmt = this.db.prepare(`UPDATE scheduled_messages SET ${fields.join(', ')} WHERE id = ?`);
|
|
1107
|
+
stmt.run(...values);
|
|
1108
|
+
}
|
|
1109
|
+
findDue(limit = 50) {
|
|
1110
|
+
const now = Date.now();
|
|
1111
|
+
const stmt = this.db.prepare(`
|
|
1112
|
+
SELECT * FROM scheduled_messages
|
|
1113
|
+
WHERE status = 'pending' AND scheduled_at <= ?
|
|
1114
|
+
ORDER BY scheduled_at ASC
|
|
1115
|
+
LIMIT ?
|
|
1116
|
+
`);
|
|
1117
|
+
const rows = stmt.all(now, limit);
|
|
1118
|
+
return rows.map(row => this.mapRowToItem(row));
|
|
1119
|
+
}
|
|
1120
|
+
findById(id) {
|
|
1121
|
+
const stmt = this.db.prepare('SELECT * FROM scheduled_messages WHERE id = ?');
|
|
1122
|
+
const row = stmt.get(id);
|
|
1123
|
+
return row ? this.mapRowToItem(row) : undefined;
|
|
1124
|
+
}
|
|
1125
|
+
findByChatId(channelType, chatId) {
|
|
1126
|
+
const stmt = this.db.prepare(`
|
|
1127
|
+
SELECT * FROM scheduled_messages
|
|
1128
|
+
WHERE channel_type = ? AND chat_id = ? AND status = 'pending'
|
|
1129
|
+
ORDER BY scheduled_at ASC
|
|
1130
|
+
`);
|
|
1131
|
+
const rows = stmt.all(channelType, chatId);
|
|
1132
|
+
return rows.map(row => this.mapRowToItem(row));
|
|
1133
|
+
}
|
|
1134
|
+
cancel(id) {
|
|
1135
|
+
const stmt = this.db.prepare("UPDATE scheduled_messages SET status = 'cancelled' WHERE id = ? AND status = 'pending'");
|
|
1136
|
+
stmt.run(id);
|
|
1137
|
+
}
|
|
1138
|
+
delete(id) {
|
|
1139
|
+
const stmt = this.db.prepare('DELETE FROM scheduled_messages WHERE id = ?');
|
|
1140
|
+
stmt.run(id);
|
|
1141
|
+
}
|
|
1142
|
+
mapRowToItem(row) {
|
|
1143
|
+
return {
|
|
1144
|
+
id: row.id,
|
|
1145
|
+
channelType: row.channel_type,
|
|
1146
|
+
chatId: row.chat_id,
|
|
1147
|
+
message: safeJsonParse(row.message, {}, 'scheduled.message'),
|
|
1148
|
+
scheduledAt: row.scheduled_at,
|
|
1149
|
+
status: row.status,
|
|
1150
|
+
sentMessageId: row.sent_message_id || undefined,
|
|
1151
|
+
error: row.error || undefined,
|
|
1152
|
+
createdAt: row.created_at,
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
exports.ScheduledMessageRepository = ScheduledMessageRepository;
|
|
1157
|
+
class DeliveryTrackingRepository {
|
|
1158
|
+
constructor(db) {
|
|
1159
|
+
this.db = db;
|
|
1160
|
+
}
|
|
1161
|
+
create(item) {
|
|
1162
|
+
const newItem = {
|
|
1163
|
+
...item,
|
|
1164
|
+
id: (0, uuid_1.v4)(),
|
|
1165
|
+
createdAt: Date.now(),
|
|
1166
|
+
};
|
|
1167
|
+
const stmt = this.db.prepare(`
|
|
1168
|
+
INSERT INTO delivery_tracking (id, channel_type, chat_id, message_id, status, sent_at, delivered_at, read_at, error, created_at)
|
|
1169
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1170
|
+
`);
|
|
1171
|
+
stmt.run(newItem.id, newItem.channelType, newItem.chatId, newItem.messageId, newItem.status, newItem.sentAt || null, newItem.deliveredAt || null, newItem.readAt || null, newItem.error || null, newItem.createdAt);
|
|
1172
|
+
return newItem;
|
|
1173
|
+
}
|
|
1174
|
+
update(id, updates) {
|
|
1175
|
+
const fields = [];
|
|
1176
|
+
const values = [];
|
|
1177
|
+
if (updates.status !== undefined) {
|
|
1178
|
+
fields.push('status = ?');
|
|
1179
|
+
values.push(updates.status);
|
|
1180
|
+
}
|
|
1181
|
+
if (updates.sentAt !== undefined) {
|
|
1182
|
+
fields.push('sent_at = ?');
|
|
1183
|
+
values.push(updates.sentAt);
|
|
1184
|
+
}
|
|
1185
|
+
if (updates.deliveredAt !== undefined) {
|
|
1186
|
+
fields.push('delivered_at = ?');
|
|
1187
|
+
values.push(updates.deliveredAt);
|
|
1188
|
+
}
|
|
1189
|
+
if (updates.readAt !== undefined) {
|
|
1190
|
+
fields.push('read_at = ?');
|
|
1191
|
+
values.push(updates.readAt);
|
|
1192
|
+
}
|
|
1193
|
+
if (updates.error !== undefined) {
|
|
1194
|
+
fields.push('error = ?');
|
|
1195
|
+
values.push(updates.error);
|
|
1196
|
+
}
|
|
1197
|
+
if (fields.length === 0)
|
|
1198
|
+
return;
|
|
1199
|
+
values.push(id);
|
|
1200
|
+
const stmt = this.db.prepare(`UPDATE delivery_tracking SET ${fields.join(', ')} WHERE id = ?`);
|
|
1201
|
+
stmt.run(...values);
|
|
1202
|
+
}
|
|
1203
|
+
findByMessageId(messageId) {
|
|
1204
|
+
const stmt = this.db.prepare('SELECT * FROM delivery_tracking WHERE message_id = ?');
|
|
1205
|
+
const row = stmt.get(messageId);
|
|
1206
|
+
return row ? this.mapRowToItem(row) : undefined;
|
|
1207
|
+
}
|
|
1208
|
+
findByChatId(channelType, chatId, limit = 50) {
|
|
1209
|
+
const stmt = this.db.prepare(`
|
|
1210
|
+
SELECT * FROM delivery_tracking
|
|
1211
|
+
WHERE channel_type = ? AND chat_id = ?
|
|
1212
|
+
ORDER BY created_at DESC
|
|
1213
|
+
LIMIT ?
|
|
1214
|
+
`);
|
|
1215
|
+
const rows = stmt.all(channelType, chatId, limit);
|
|
1216
|
+
return rows.map(row => this.mapRowToItem(row));
|
|
1217
|
+
}
|
|
1218
|
+
deleteOld(olderThanMs) {
|
|
1219
|
+
const cutoff = Date.now() - olderThanMs;
|
|
1220
|
+
const stmt = this.db.prepare('DELETE FROM delivery_tracking WHERE created_at < ?');
|
|
1221
|
+
const result = stmt.run(cutoff);
|
|
1222
|
+
return result.changes;
|
|
1223
|
+
}
|
|
1224
|
+
mapRowToItem(row) {
|
|
1225
|
+
return {
|
|
1226
|
+
id: row.id,
|
|
1227
|
+
channelType: row.channel_type,
|
|
1228
|
+
chatId: row.chat_id,
|
|
1229
|
+
messageId: row.message_id,
|
|
1230
|
+
status: row.status,
|
|
1231
|
+
sentAt: row.sent_at || undefined,
|
|
1232
|
+
deliveredAt: row.delivered_at || undefined,
|
|
1233
|
+
readAt: row.read_at || undefined,
|
|
1234
|
+
error: row.error || undefined,
|
|
1235
|
+
createdAt: row.created_at,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
exports.DeliveryTrackingRepository = DeliveryTrackingRepository;
|
|
1240
|
+
class RateLimitRepository {
|
|
1241
|
+
constructor(db) {
|
|
1242
|
+
this.db = db;
|
|
1243
|
+
}
|
|
1244
|
+
getOrCreate(channelType, userId) {
|
|
1245
|
+
const stmt = this.db.prepare('SELECT * FROM rate_limits WHERE channel_type = ? AND user_id = ?');
|
|
1246
|
+
const row = stmt.get(channelType, userId);
|
|
1247
|
+
if (row) {
|
|
1248
|
+
return this.mapRowToItem(row);
|
|
1249
|
+
}
|
|
1250
|
+
// Create new record
|
|
1251
|
+
const newItem = {
|
|
1252
|
+
id: (0, uuid_1.v4)(),
|
|
1253
|
+
channelType,
|
|
1254
|
+
userId,
|
|
1255
|
+
messageCount: 0,
|
|
1256
|
+
windowStart: Date.now(),
|
|
1257
|
+
isLimited: false,
|
|
1258
|
+
};
|
|
1259
|
+
const insertStmt = this.db.prepare(`
|
|
1260
|
+
INSERT INTO rate_limits (id, channel_type, user_id, message_count, window_start, is_limited, limit_expires_at)
|
|
1261
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1262
|
+
`);
|
|
1263
|
+
insertStmt.run(newItem.id, newItem.channelType, newItem.userId, newItem.messageCount, newItem.windowStart, newItem.isLimited ? 1 : 0, newItem.limitExpiresAt || null);
|
|
1264
|
+
return newItem;
|
|
1265
|
+
}
|
|
1266
|
+
update(channelType, userId, updates) {
|
|
1267
|
+
const fields = [];
|
|
1268
|
+
const values = [];
|
|
1269
|
+
if (updates.messageCount !== undefined) {
|
|
1270
|
+
fields.push('message_count = ?');
|
|
1271
|
+
values.push(updates.messageCount);
|
|
1272
|
+
}
|
|
1273
|
+
if (updates.windowStart !== undefined) {
|
|
1274
|
+
fields.push('window_start = ?');
|
|
1275
|
+
values.push(updates.windowStart);
|
|
1276
|
+
}
|
|
1277
|
+
if (updates.isLimited !== undefined) {
|
|
1278
|
+
fields.push('is_limited = ?');
|
|
1279
|
+
values.push(updates.isLimited ? 1 : 0);
|
|
1280
|
+
}
|
|
1281
|
+
if (updates.limitExpiresAt !== undefined) {
|
|
1282
|
+
fields.push('limit_expires_at = ?');
|
|
1283
|
+
values.push(updates.limitExpiresAt);
|
|
1284
|
+
}
|
|
1285
|
+
if (fields.length === 0)
|
|
1286
|
+
return;
|
|
1287
|
+
values.push(channelType, userId);
|
|
1288
|
+
const stmt = this.db.prepare(`UPDATE rate_limits SET ${fields.join(', ')} WHERE channel_type = ? AND user_id = ?`);
|
|
1289
|
+
stmt.run(...values);
|
|
1290
|
+
}
|
|
1291
|
+
resetWindow(channelType, userId) {
|
|
1292
|
+
const stmt = this.db.prepare(`
|
|
1293
|
+
UPDATE rate_limits
|
|
1294
|
+
SET message_count = 0, window_start = ?, is_limited = 0, limit_expires_at = NULL
|
|
1295
|
+
WHERE channel_type = ? AND user_id = ?
|
|
1296
|
+
`);
|
|
1297
|
+
stmt.run(Date.now(), channelType, userId);
|
|
1298
|
+
}
|
|
1299
|
+
mapRowToItem(row) {
|
|
1300
|
+
return {
|
|
1301
|
+
id: row.id,
|
|
1302
|
+
channelType: row.channel_type,
|
|
1303
|
+
userId: row.user_id,
|
|
1304
|
+
messageCount: row.message_count,
|
|
1305
|
+
windowStart: row.window_start,
|
|
1306
|
+
isLimited: row.is_limited === 1,
|
|
1307
|
+
limitExpiresAt: row.limit_expires_at || undefined,
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
exports.RateLimitRepository = RateLimitRepository;
|
|
1312
|
+
class AuditLogRepository {
|
|
1313
|
+
constructor(db) {
|
|
1314
|
+
this.db = db;
|
|
1315
|
+
}
|
|
1316
|
+
log(entry) {
|
|
1317
|
+
const newEntry = {
|
|
1318
|
+
...entry,
|
|
1319
|
+
id: (0, uuid_1.v4)(),
|
|
1320
|
+
timestamp: Date.now(),
|
|
1321
|
+
};
|
|
1322
|
+
const stmt = this.db.prepare(`
|
|
1323
|
+
INSERT INTO audit_log (id, timestamp, action, channel_type, user_id, chat_id, details, severity)
|
|
1324
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1325
|
+
`);
|
|
1326
|
+
stmt.run(newEntry.id, newEntry.timestamp, newEntry.action, newEntry.channelType || null, newEntry.userId || null, newEntry.chatId || null, newEntry.details ? JSON.stringify(newEntry.details) : null, newEntry.severity);
|
|
1327
|
+
return newEntry;
|
|
1328
|
+
}
|
|
1329
|
+
find(options) {
|
|
1330
|
+
const conditions = [];
|
|
1331
|
+
const values = [];
|
|
1332
|
+
if (options.action) {
|
|
1333
|
+
conditions.push('action = ?');
|
|
1334
|
+
values.push(options.action);
|
|
1335
|
+
}
|
|
1336
|
+
if (options.channelType) {
|
|
1337
|
+
conditions.push('channel_type = ?');
|
|
1338
|
+
values.push(options.channelType);
|
|
1339
|
+
}
|
|
1340
|
+
if (options.userId) {
|
|
1341
|
+
conditions.push('user_id = ?');
|
|
1342
|
+
values.push(options.userId);
|
|
1343
|
+
}
|
|
1344
|
+
if (options.chatId) {
|
|
1345
|
+
conditions.push('chat_id = ?');
|
|
1346
|
+
values.push(options.chatId);
|
|
1347
|
+
}
|
|
1348
|
+
if (options.fromTimestamp) {
|
|
1349
|
+
conditions.push('timestamp >= ?');
|
|
1350
|
+
values.push(options.fromTimestamp);
|
|
1351
|
+
}
|
|
1352
|
+
if (options.toTimestamp) {
|
|
1353
|
+
conditions.push('timestamp <= ?');
|
|
1354
|
+
values.push(options.toTimestamp);
|
|
1355
|
+
}
|
|
1356
|
+
if (options.severity) {
|
|
1357
|
+
conditions.push('severity = ?');
|
|
1358
|
+
values.push(options.severity);
|
|
1359
|
+
}
|
|
1360
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
1361
|
+
const limit = options.limit || 100;
|
|
1362
|
+
const offset = options.offset || 0;
|
|
1363
|
+
const stmt = this.db.prepare(`
|
|
1364
|
+
SELECT * FROM audit_log
|
|
1365
|
+
${whereClause}
|
|
1366
|
+
ORDER BY timestamp DESC
|
|
1367
|
+
LIMIT ? OFFSET ?
|
|
1368
|
+
`);
|
|
1369
|
+
values.push(limit, offset);
|
|
1370
|
+
const rows = stmt.all(...values);
|
|
1371
|
+
return rows.map(row => this.mapRowToEntry(row));
|
|
1372
|
+
}
|
|
1373
|
+
deleteOld(olderThanMs) {
|
|
1374
|
+
const cutoff = Date.now() - olderThanMs;
|
|
1375
|
+
const stmt = this.db.prepare('DELETE FROM audit_log WHERE timestamp < ?');
|
|
1376
|
+
const result = stmt.run(cutoff);
|
|
1377
|
+
return result.changes;
|
|
1378
|
+
}
|
|
1379
|
+
mapRowToEntry(row) {
|
|
1380
|
+
return {
|
|
1381
|
+
id: row.id,
|
|
1382
|
+
timestamp: row.timestamp,
|
|
1383
|
+
action: row.action,
|
|
1384
|
+
channelType: row.channel_type || undefined,
|
|
1385
|
+
userId: row.user_id || undefined,
|
|
1386
|
+
chatId: row.chat_id || undefined,
|
|
1387
|
+
details: row.details ? safeJsonParse(row.details, undefined, 'audit.details') : undefined,
|
|
1388
|
+
severity: row.severity,
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
exports.AuditLogRepository = AuditLogRepository;
|
|
1393
|
+
class MemoryRepository {
|
|
1394
|
+
constructor(db) {
|
|
1395
|
+
this.db = db;
|
|
1396
|
+
}
|
|
1397
|
+
create(memory) {
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
const newMemory = {
|
|
1400
|
+
...memory,
|
|
1401
|
+
id: (0, uuid_1.v4)(),
|
|
1402
|
+
createdAt: now,
|
|
1403
|
+
updatedAt: now,
|
|
1404
|
+
};
|
|
1405
|
+
const stmt = this.db.prepare(`
|
|
1406
|
+
INSERT INTO memories (id, workspace_id, task_id, type, content, summary, tokens, is_compressed, is_private, created_at, updated_at)
|
|
1407
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1408
|
+
`);
|
|
1409
|
+
stmt.run(newMemory.id, newMemory.workspaceId, newMemory.taskId || null, newMemory.type, newMemory.content, newMemory.summary || null, newMemory.tokens, newMemory.isCompressed ? 1 : 0, newMemory.isPrivate ? 1 : 0, newMemory.createdAt, newMemory.updatedAt);
|
|
1410
|
+
return newMemory;
|
|
1411
|
+
}
|
|
1412
|
+
update(id, updates) {
|
|
1413
|
+
const fields = [];
|
|
1414
|
+
const values = [];
|
|
1415
|
+
if (updates.summary !== undefined) {
|
|
1416
|
+
fields.push('summary = ?');
|
|
1417
|
+
values.push(updates.summary);
|
|
1418
|
+
}
|
|
1419
|
+
if (updates.tokens !== undefined) {
|
|
1420
|
+
fields.push('tokens = ?');
|
|
1421
|
+
values.push(updates.tokens);
|
|
1422
|
+
}
|
|
1423
|
+
if (updates.isCompressed !== undefined) {
|
|
1424
|
+
fields.push('is_compressed = ?');
|
|
1425
|
+
values.push(updates.isCompressed ? 1 : 0);
|
|
1426
|
+
}
|
|
1427
|
+
if (fields.length === 0)
|
|
1428
|
+
return;
|
|
1429
|
+
fields.push('updated_at = ?');
|
|
1430
|
+
values.push(Date.now());
|
|
1431
|
+
values.push(id);
|
|
1432
|
+
const stmt = this.db.prepare(`UPDATE memories SET ${fields.join(', ')} WHERE id = ?`);
|
|
1433
|
+
stmt.run(...values);
|
|
1434
|
+
}
|
|
1435
|
+
findById(id) {
|
|
1436
|
+
const stmt = this.db.prepare('SELECT * FROM memories WHERE id = ?');
|
|
1437
|
+
const row = stmt.get(id);
|
|
1438
|
+
return row ? this.mapRowToMemory(row) : undefined;
|
|
1439
|
+
}
|
|
1440
|
+
findByIds(ids) {
|
|
1441
|
+
if (ids.length === 0)
|
|
1442
|
+
return [];
|
|
1443
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
1444
|
+
const stmt = this.db.prepare(`SELECT * FROM memories WHERE id IN (${placeholders})`);
|
|
1445
|
+
const rows = stmt.all(...ids);
|
|
1446
|
+
return rows.map(row => this.mapRowToMemory(row));
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Layer 1: Search returns IDs + brief snippets (~50 tokens each)
|
|
1450
|
+
* Uses FTS5 for full-text search with relevance ranking
|
|
1451
|
+
*/
|
|
1452
|
+
search(workspaceId, query, limit = 20) {
|
|
1453
|
+
try {
|
|
1454
|
+
// Try FTS5 search first
|
|
1455
|
+
const stmt = this.db.prepare(`
|
|
1456
|
+
SELECT m.id, m.summary, m.content, m.type, m.created_at, m.task_id,
|
|
1457
|
+
bm25(memories_fts) as score
|
|
1458
|
+
FROM memories_fts f
|
|
1459
|
+
JOIN memories m ON f.rowid = m.rowid
|
|
1460
|
+
WHERE memories_fts MATCH ? AND m.workspace_id = ? AND m.is_private = 0
|
|
1461
|
+
ORDER BY score
|
|
1462
|
+
LIMIT ?
|
|
1463
|
+
`);
|
|
1464
|
+
const rows = stmt.all(query, workspaceId, limit);
|
|
1465
|
+
return rows.map(row => ({
|
|
1466
|
+
id: row.id,
|
|
1467
|
+
snippet: row.summary || this.truncateToSnippet(row.content, 200),
|
|
1468
|
+
type: row.type,
|
|
1469
|
+
relevanceScore: Math.abs(row.score),
|
|
1470
|
+
createdAt: row.created_at,
|
|
1471
|
+
taskId: row.task_id || undefined,
|
|
1472
|
+
}));
|
|
1473
|
+
}
|
|
1474
|
+
catch {
|
|
1475
|
+
// Fall back to LIKE search if FTS5 is not available
|
|
1476
|
+
const stmt = this.db.prepare(`
|
|
1477
|
+
SELECT id, summary, content, type, created_at, task_id
|
|
1478
|
+
FROM memories
|
|
1479
|
+
WHERE workspace_id = ? AND is_private = 0
|
|
1480
|
+
AND (content LIKE ? OR summary LIKE ?)
|
|
1481
|
+
ORDER BY created_at DESC
|
|
1482
|
+
LIMIT ?
|
|
1483
|
+
`);
|
|
1484
|
+
const likeQuery = `%${query}%`;
|
|
1485
|
+
const rows = stmt.all(workspaceId, likeQuery, likeQuery, limit);
|
|
1486
|
+
return rows.map(row => ({
|
|
1487
|
+
id: row.id,
|
|
1488
|
+
snippet: row.summary || this.truncateToSnippet(row.content, 200),
|
|
1489
|
+
type: row.type,
|
|
1490
|
+
relevanceScore: 1,
|
|
1491
|
+
createdAt: row.created_at,
|
|
1492
|
+
taskId: row.task_id || undefined,
|
|
1493
|
+
}));
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Layer 2: Get timeline context around a specific memory
|
|
1498
|
+
* Returns surrounding memories within a time window
|
|
1499
|
+
*/
|
|
1500
|
+
getTimelineContext(memoryId, windowSize = 5) {
|
|
1501
|
+
const memory = this.findById(memoryId);
|
|
1502
|
+
if (!memory)
|
|
1503
|
+
return [];
|
|
1504
|
+
const stmt = this.db.prepare(`
|
|
1505
|
+
SELECT id, content, type, created_at, task_id
|
|
1506
|
+
FROM memories
|
|
1507
|
+
WHERE workspace_id = ? AND is_private = 0
|
|
1508
|
+
AND created_at BETWEEN ? AND ?
|
|
1509
|
+
ORDER BY created_at ASC
|
|
1510
|
+
LIMIT ?
|
|
1511
|
+
`);
|
|
1512
|
+
const timeWindow = 30 * 60 * 1000; // 30 minutes
|
|
1513
|
+
const rows = stmt.all(memory.workspaceId, memory.createdAt - timeWindow, memory.createdAt + timeWindow, windowSize * 2 + 1);
|
|
1514
|
+
return rows.map(row => ({
|
|
1515
|
+
id: row.id,
|
|
1516
|
+
content: row.content,
|
|
1517
|
+
type: row.type,
|
|
1518
|
+
createdAt: row.created_at,
|
|
1519
|
+
taskId: row.task_id || undefined,
|
|
1520
|
+
}));
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Layer 3: Get full details for selected IDs
|
|
1524
|
+
* Only called for specific memories when full content is needed
|
|
1525
|
+
*/
|
|
1526
|
+
getFullDetails(ids) {
|
|
1527
|
+
return this.findByIds(ids);
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Get recent memories for context injection
|
|
1531
|
+
*/
|
|
1532
|
+
getRecentForWorkspace(workspaceId, limit = 10) {
|
|
1533
|
+
const stmt = this.db.prepare(`
|
|
1534
|
+
SELECT * FROM memories
|
|
1535
|
+
WHERE workspace_id = ? AND is_private = 0
|
|
1536
|
+
ORDER BY created_at DESC
|
|
1537
|
+
LIMIT ?
|
|
1538
|
+
`);
|
|
1539
|
+
const rows = stmt.all(workspaceId, limit);
|
|
1540
|
+
return rows.map(row => this.mapRowToMemory(row));
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get uncompressed memories for batch compression
|
|
1544
|
+
*/
|
|
1545
|
+
getUncompressed(limit = 50) {
|
|
1546
|
+
const stmt = this.db.prepare(`
|
|
1547
|
+
SELECT * FROM memories
|
|
1548
|
+
WHERE is_compressed = 0 AND summary IS NULL
|
|
1549
|
+
ORDER BY created_at ASC
|
|
1550
|
+
LIMIT ?
|
|
1551
|
+
`);
|
|
1552
|
+
const rows = stmt.all(limit);
|
|
1553
|
+
return rows.map(row => this.mapRowToMemory(row));
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Find memories by workspace
|
|
1557
|
+
*/
|
|
1558
|
+
findByWorkspace(workspaceId, limit = 100, offset = 0) {
|
|
1559
|
+
const stmt = this.db.prepare(`
|
|
1560
|
+
SELECT * FROM memories
|
|
1561
|
+
WHERE workspace_id = ?
|
|
1562
|
+
ORDER BY created_at DESC
|
|
1563
|
+
LIMIT ? OFFSET ?
|
|
1564
|
+
`);
|
|
1565
|
+
const rows = stmt.all(workspaceId, limit, offset);
|
|
1566
|
+
return rows.map(row => this.mapRowToMemory(row));
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Find memories by task
|
|
1570
|
+
*/
|
|
1571
|
+
findByTask(taskId) {
|
|
1572
|
+
const stmt = this.db.prepare(`
|
|
1573
|
+
SELECT * FROM memories
|
|
1574
|
+
WHERE task_id = ?
|
|
1575
|
+
ORDER BY created_at ASC
|
|
1576
|
+
`);
|
|
1577
|
+
const rows = stmt.all(taskId);
|
|
1578
|
+
return rows.map(row => this.mapRowToMemory(row));
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Cleanup old memories based on retention policy
|
|
1582
|
+
*/
|
|
1583
|
+
deleteOlderThan(workspaceId, cutoffTimestamp) {
|
|
1584
|
+
const stmt = this.db.prepare(`
|
|
1585
|
+
DELETE FROM memories
|
|
1586
|
+
WHERE workspace_id = ? AND created_at < ?
|
|
1587
|
+
`);
|
|
1588
|
+
const result = stmt.run(workspaceId, cutoffTimestamp);
|
|
1589
|
+
return result.changes;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Delete all memories for a workspace
|
|
1593
|
+
*/
|
|
1594
|
+
deleteByWorkspace(workspaceId) {
|
|
1595
|
+
const stmt = this.db.prepare('DELETE FROM memories WHERE workspace_id = ?');
|
|
1596
|
+
const result = stmt.run(workspaceId);
|
|
1597
|
+
return result.changes;
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Get storage statistics for a workspace
|
|
1601
|
+
*/
|
|
1602
|
+
getStats(workspaceId) {
|
|
1603
|
+
const stmt = this.db.prepare(`
|
|
1604
|
+
SELECT COUNT(*) as count,
|
|
1605
|
+
COALESCE(SUM(tokens), 0) as total_tokens,
|
|
1606
|
+
SUM(CASE WHEN is_compressed = 1 THEN 1 ELSE 0 END) as compressed_count
|
|
1607
|
+
FROM memories
|
|
1608
|
+
WHERE workspace_id = ?
|
|
1609
|
+
`);
|
|
1610
|
+
const row = stmt.get(workspaceId);
|
|
1611
|
+
const count = row.count;
|
|
1612
|
+
const compressedCount = row.compressed_count;
|
|
1613
|
+
return {
|
|
1614
|
+
count,
|
|
1615
|
+
totalTokens: row.total_tokens,
|
|
1616
|
+
compressedCount,
|
|
1617
|
+
compressionRatio: count > 0 ? compressedCount / count : 0,
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
truncateToSnippet(content, maxChars) {
|
|
1621
|
+
if (content.length <= maxChars)
|
|
1622
|
+
return content;
|
|
1623
|
+
return content.slice(0, maxChars - 3) + '...';
|
|
1624
|
+
}
|
|
1625
|
+
mapRowToMemory(row) {
|
|
1626
|
+
return {
|
|
1627
|
+
id: row.id,
|
|
1628
|
+
workspaceId: row.workspace_id,
|
|
1629
|
+
taskId: row.task_id || undefined,
|
|
1630
|
+
type: row.type,
|
|
1631
|
+
content: row.content,
|
|
1632
|
+
summary: row.summary || undefined,
|
|
1633
|
+
tokens: row.tokens,
|
|
1634
|
+
isCompressed: row.is_compressed === 1,
|
|
1635
|
+
isPrivate: row.is_private === 1,
|
|
1636
|
+
createdAt: row.created_at,
|
|
1637
|
+
updatedAt: row.updated_at,
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
exports.MemoryRepository = MemoryRepository;
|
|
1642
|
+
class MemorySummaryRepository {
|
|
1643
|
+
constructor(db) {
|
|
1644
|
+
this.db = db;
|
|
1645
|
+
}
|
|
1646
|
+
create(summary) {
|
|
1647
|
+
const newSummary = {
|
|
1648
|
+
...summary,
|
|
1649
|
+
id: (0, uuid_1.v4)(),
|
|
1650
|
+
createdAt: Date.now(),
|
|
1651
|
+
};
|
|
1652
|
+
const stmt = this.db.prepare(`
|
|
1653
|
+
INSERT INTO memory_summaries (id, workspace_id, time_period, period_start, period_end, summary, memory_ids, tokens, created_at)
|
|
1654
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1655
|
+
`);
|
|
1656
|
+
stmt.run(newSummary.id, newSummary.workspaceId, newSummary.timePeriod, newSummary.periodStart, newSummary.periodEnd, newSummary.summary, JSON.stringify(newSummary.memoryIds), newSummary.tokens, newSummary.createdAt);
|
|
1657
|
+
return newSummary;
|
|
1658
|
+
}
|
|
1659
|
+
findByWorkspaceAndPeriod(workspaceId, timePeriod, limit = 10) {
|
|
1660
|
+
const stmt = this.db.prepare(`
|
|
1661
|
+
SELECT * FROM memory_summaries
|
|
1662
|
+
WHERE workspace_id = ? AND time_period = ?
|
|
1663
|
+
ORDER BY period_start DESC
|
|
1664
|
+
LIMIT ?
|
|
1665
|
+
`);
|
|
1666
|
+
const rows = stmt.all(workspaceId, timePeriod, limit);
|
|
1667
|
+
return rows.map(row => this.mapRowToSummary(row));
|
|
1668
|
+
}
|
|
1669
|
+
findByWorkspace(workspaceId, limit = 50) {
|
|
1670
|
+
const stmt = this.db.prepare(`
|
|
1671
|
+
SELECT * FROM memory_summaries
|
|
1672
|
+
WHERE workspace_id = ?
|
|
1673
|
+
ORDER BY period_start DESC
|
|
1674
|
+
LIMIT ?
|
|
1675
|
+
`);
|
|
1676
|
+
const rows = stmt.all(workspaceId, limit);
|
|
1677
|
+
return rows.map(row => this.mapRowToSummary(row));
|
|
1678
|
+
}
|
|
1679
|
+
deleteByWorkspace(workspaceId) {
|
|
1680
|
+
const stmt = this.db.prepare('DELETE FROM memory_summaries WHERE workspace_id = ?');
|
|
1681
|
+
const result = stmt.run(workspaceId);
|
|
1682
|
+
return result.changes;
|
|
1683
|
+
}
|
|
1684
|
+
mapRowToSummary(row) {
|
|
1685
|
+
return {
|
|
1686
|
+
id: row.id,
|
|
1687
|
+
workspaceId: row.workspace_id,
|
|
1688
|
+
timePeriod: row.time_period,
|
|
1689
|
+
periodStart: row.period_start,
|
|
1690
|
+
periodEnd: row.period_end,
|
|
1691
|
+
summary: row.summary,
|
|
1692
|
+
memoryIds: safeJsonParse(row.memory_ids, [], 'memorySummary.memoryIds'),
|
|
1693
|
+
tokens: row.tokens,
|
|
1694
|
+
createdAt: row.created_at,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
exports.MemorySummaryRepository = MemorySummaryRepository;
|
|
1699
|
+
class MemorySettingsRepository {
|
|
1700
|
+
constructor(db) {
|
|
1701
|
+
this.db = db;
|
|
1702
|
+
}
|
|
1703
|
+
getOrCreate(workspaceId) {
|
|
1704
|
+
const stmt = this.db.prepare('SELECT * FROM memory_settings WHERE workspace_id = ?');
|
|
1705
|
+
const row = stmt.get(workspaceId);
|
|
1706
|
+
if (row) {
|
|
1707
|
+
return this.mapRowToSettings(row);
|
|
1708
|
+
}
|
|
1709
|
+
// Create default settings
|
|
1710
|
+
const defaults = {
|
|
1711
|
+
workspaceId,
|
|
1712
|
+
enabled: true,
|
|
1713
|
+
autoCapture: true,
|
|
1714
|
+
compressionEnabled: true,
|
|
1715
|
+
retentionDays: 90,
|
|
1716
|
+
maxStorageMb: 100,
|
|
1717
|
+
privacyMode: 'normal',
|
|
1718
|
+
excludedPatterns: [],
|
|
1719
|
+
};
|
|
1720
|
+
const insertStmt = this.db.prepare(`
|
|
1721
|
+
INSERT INTO memory_settings (workspace_id, enabled, auto_capture, compression_enabled, retention_days, max_storage_mb, privacy_mode, excluded_patterns)
|
|
1722
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1723
|
+
`);
|
|
1724
|
+
insertStmt.run(defaults.workspaceId, defaults.enabled ? 1 : 0, defaults.autoCapture ? 1 : 0, defaults.compressionEnabled ? 1 : 0, defaults.retentionDays, defaults.maxStorageMb, defaults.privacyMode, JSON.stringify(defaults.excludedPatterns));
|
|
1725
|
+
return defaults;
|
|
1726
|
+
}
|
|
1727
|
+
update(workspaceId, updates) {
|
|
1728
|
+
const fields = [];
|
|
1729
|
+
const values = [];
|
|
1730
|
+
if (updates.enabled !== undefined) {
|
|
1731
|
+
fields.push('enabled = ?');
|
|
1732
|
+
values.push(updates.enabled ? 1 : 0);
|
|
1733
|
+
}
|
|
1734
|
+
if (updates.autoCapture !== undefined) {
|
|
1735
|
+
fields.push('auto_capture = ?');
|
|
1736
|
+
values.push(updates.autoCapture ? 1 : 0);
|
|
1737
|
+
}
|
|
1738
|
+
if (updates.compressionEnabled !== undefined) {
|
|
1739
|
+
fields.push('compression_enabled = ?');
|
|
1740
|
+
values.push(updates.compressionEnabled ? 1 : 0);
|
|
1741
|
+
}
|
|
1742
|
+
if (updates.retentionDays !== undefined) {
|
|
1743
|
+
fields.push('retention_days = ?');
|
|
1744
|
+
values.push(updates.retentionDays);
|
|
1745
|
+
}
|
|
1746
|
+
if (updates.maxStorageMb !== undefined) {
|
|
1747
|
+
fields.push('max_storage_mb = ?');
|
|
1748
|
+
values.push(updates.maxStorageMb);
|
|
1749
|
+
}
|
|
1750
|
+
if (updates.privacyMode !== undefined) {
|
|
1751
|
+
fields.push('privacy_mode = ?');
|
|
1752
|
+
values.push(updates.privacyMode);
|
|
1753
|
+
}
|
|
1754
|
+
if (updates.excludedPatterns !== undefined) {
|
|
1755
|
+
fields.push('excluded_patterns = ?');
|
|
1756
|
+
values.push(JSON.stringify(updates.excludedPatterns));
|
|
1757
|
+
}
|
|
1758
|
+
if (fields.length === 0)
|
|
1759
|
+
return;
|
|
1760
|
+
values.push(workspaceId);
|
|
1761
|
+
const stmt = this.db.prepare(`UPDATE memory_settings SET ${fields.join(', ')} WHERE workspace_id = ?`);
|
|
1762
|
+
stmt.run(...values);
|
|
1763
|
+
}
|
|
1764
|
+
delete(workspaceId) {
|
|
1765
|
+
const stmt = this.db.prepare('DELETE FROM memory_settings WHERE workspace_id = ?');
|
|
1766
|
+
stmt.run(workspaceId);
|
|
1767
|
+
}
|
|
1768
|
+
mapRowToSettings(row) {
|
|
1769
|
+
return {
|
|
1770
|
+
workspaceId: row.workspace_id,
|
|
1771
|
+
enabled: row.enabled === 1,
|
|
1772
|
+
autoCapture: row.auto_capture === 1,
|
|
1773
|
+
compressionEnabled: row.compression_enabled === 1,
|
|
1774
|
+
retentionDays: row.retention_days,
|
|
1775
|
+
maxStorageMb: row.max_storage_mb,
|
|
1776
|
+
privacyMode: row.privacy_mode,
|
|
1777
|
+
excludedPatterns: safeJsonParse(row.excluded_patterns, [], 'memorySettings.excludedPatterns'),
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
exports.MemorySettingsRepository = MemorySettingsRepository;
|