crewly 1.6.1 → 1.6.2
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/config/roles/orchestrator/prompt.md +16 -0
- package/config/skills/agent/core/get-my-active-work/SKILL.md +101 -0
- package/config/skills/agent/core/get-my-active-work/execute.sh +122 -0
- package/config/skills/agent/core/record-learning/SKILL.md +29 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +41 -0
- package/config/skills/agent/core/reply-channel/execute.sh +165 -0
- package/config/skills/agent/core/reply-channel/execute.test.sh +148 -0
- package/config/skills/agent/remote-browser/execute.sh +296 -14
- package/config/skills/agent/remote-browser/execute.test.sh +482 -0
- package/config/skills/orchestrator/send-message/SKILL.md +30 -7
- package/config/skills/orchestrator/team-health-scan/SKILL.md +98 -0
- package/config/skills/orchestrator/team-health-scan/execute.sh +44 -0
- package/config/skills/registry.json +62 -1
- package/config/sops/developer/git-workflow.md +38 -3
- package/dist/backend/backend/src/constants.d.ts +69 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +69 -2
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts +53 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.js +92 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js +18 -1
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +68 -0
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +233 -5
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +10 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +8 -3
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +132 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +401 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +29 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +39 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.d.ts +8 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.js +8 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts +13 -13
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js +74 -234
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/request/request.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/request/request.controller.js +4 -6
- package/dist/backend/backend/src/controllers/request/request.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts +43 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js +200 -72
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +46 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts +59 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.js +127 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts +13 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.js +20 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts +9 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +233 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +40 -6
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts +498 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.js +759 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +25 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +193 -57
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +9 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +35 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +8 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/types.js +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.js +2 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux.service.js +2 -1
- package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +148 -3
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +241 -2
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js +13 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js +26 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +161 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js +382 -2
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +105 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js +232 -13
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +178 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +254 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts +134 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js +232 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts +25 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js +23 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +254 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +467 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts +43 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js +54 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/config.d.ts +100 -0
- package/dist/backend/backend/src/services/chat-v2/config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/config.js +174 -0
- package/dist/backend/backend/src/services/chat-v2/config.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/index.d.ts +11 -0
- package/dist/backend/backend/src/services/chat-v2/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/index.js +12 -0
- package/dist/backend/backend/src/services/chat-v2/index.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +114 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +194 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts +100 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +351 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +132 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +281 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts +295 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/types.js +61 -0
- package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts +113 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js +179 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts +131 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js +227 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/config.service.js +3 -3
- package/dist/backend/backend/src/services/core/config.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.d.ts +7 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +15 -0
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +69 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +118 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +275 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +736 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js +18 -2
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +123 -29
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.js +304 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js +24 -4
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts +174 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js +375 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts +97 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.js +209 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.js.map +1 -0
- package/dist/backend/backend/src/services/memory/vector-store.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/vector-store.service.js +19 -4
- package/dist/backend/backend/src/services/memory/vector-store.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts +16 -5
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js +32 -5
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts +157 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.js +229 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts +141 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.js +18 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.js.map +1 -0
- package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +17 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +39 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +158 -26
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +248 -6
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +531 -51
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/team-health/index.d.ts +16 -0
- package/dist/backend/backend/src/services/team-health/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/index.js +16 -0
- package/dist/backend/backend/src/services/team-health/index.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts +52 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js +161 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts +53 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js +88 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts +44 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js +83 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts +92 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.js +328 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.d.ts +41 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.js +213 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts +46 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.js +347 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.d.ts +154 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.js +94 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts +111 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js +226 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +148 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +545 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts +499 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +1105 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts +22 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request.service.js +71 -0
- package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts +1 -0
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +22 -6
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts +19 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.js +43 -0
- package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/backend/backend/src/types/index.d.ts +22 -1
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/review-reason.types.d.ts +63 -0
- package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/review-reason.types.js +50 -0
- package/dist/backend/backend/src/types/review-reason.types.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +4 -1
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +18 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js +1 -0
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js +25 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/backend/src/utils/team.utils.d.ts +38 -0
- package/dist/backend/backend/src/utils/team.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/team.utils.js +45 -0
- package/dist/backend/backend/src/utils/team.utils.js.map +1 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts +195 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts.map +1 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.js +401 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.js.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +37 -2
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js +106 -5
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +69 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +69 -2
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/core/config.service.js +3 -3
- package/dist/cli/backend/src/services/core/config.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.d.ts +7 -0
- package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js +15 -0
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js +18 -2
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +123 -29
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js +24 -4
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +22 -1
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js +25 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/frontend/dist/assets/{index-70356616.js → index-7a4e7df5.js} +328 -326
- package/frontend/dist/assets/index-b7e59b2b.css +33 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +2 -1
- package/config/skills/orchestrator/recall/SKILL.md +0 -47
- package/config/skills/orchestrator/recall/execute.sh +0 -13
- package/config/skills/orchestrator/record-learning/SKILL.md +0 -47
- package/config/skills/orchestrator/record-learning/execute.sh +0 -13
- package/config/skills/orchestrator/remember/SKILL.md +0 -55
- package/config/skills/orchestrator/remember/execute.sh +0 -15
- package/frontend/dist/assets/index-6aaa0630.css +0 -33
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChannelStore — CRUD for `chat_channels`.
|
|
3
|
+
*
|
|
4
|
+
* All authorization is handled at a higher layer (`ChatV2Service`);
|
|
5
|
+
* this store only enforces DB-level invariants (FKs, unique indexes).
|
|
6
|
+
*
|
|
7
|
+
* @module services/chat-v2/sqlite/channel.store
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from 'crypto';
|
|
10
|
+
import { CHAT_ERROR_CODES, ChatError, } from '../types.js';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Constants — shared SELECT column list, single source of truth
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Canonical column list for `chat_channels` SELECTs. Including all Phase A
|
|
16
|
+
* columns (type / team_id / project_id / target_member_id) here keeps every
|
|
17
|
+
* read path in this store mapping to a fully-populated `ChatChannelRow`.
|
|
18
|
+
*/
|
|
19
|
+
const CHANNEL_SELECT_COLUMNS = `
|
|
20
|
+
id, agent_session, owner_user_id, name, purpose,
|
|
21
|
+
created_at, archived_at, last_message_at,
|
|
22
|
+
type, team_id, project_id, target_member_id
|
|
23
|
+
`;
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Store
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/** SQLite-backed store for chat channels. */
|
|
28
|
+
export class ChannelStore {
|
|
29
|
+
db;
|
|
30
|
+
constructor(db) {
|
|
31
|
+
this.db = db;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a new channel. Enforces the 1:1 agent-binding by catching
|
|
35
|
+
* the SQLite unique-constraint error and surfacing it as a
|
|
36
|
+
* `ChatError(agent_already_bound, 409)`.
|
|
37
|
+
*
|
|
38
|
+
* @param input - The channel creation payload
|
|
39
|
+
* @returns The inserted channel row
|
|
40
|
+
* @throws {ChatError} `agent_already_bound` (409) if the agent is already bound
|
|
41
|
+
* to another active channel.
|
|
42
|
+
*/
|
|
43
|
+
create(input) {
|
|
44
|
+
const id = input.id ?? randomUUID();
|
|
45
|
+
const createdAt = input.nowMs ?? Date.now();
|
|
46
|
+
const purpose = input.purpose ?? null;
|
|
47
|
+
const channelType = input.type ?? 'dm';
|
|
48
|
+
const teamId = input.teamId ?? null;
|
|
49
|
+
const projectId = input.projectId ?? null;
|
|
50
|
+
const targetMemberId = input.targetMemberId ?? null;
|
|
51
|
+
const stmt = this.db.prepare(`INSERT INTO chat_channels
|
|
52
|
+
(id, agent_session, owner_user_id, name, purpose, created_at,
|
|
53
|
+
type, team_id, project_id, target_member_id)
|
|
54
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
55
|
+
try {
|
|
56
|
+
stmt.run(id, input.agentSession, input.ownerUserId, input.name, purpose, createdAt, channelType, teamId, projectId, targetMemberId);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// Unique constraint means either the partial index on agent_session fired
|
|
60
|
+
// (the common case — 1:1 binding violated) or we raced an id collision.
|
|
61
|
+
// In either case it's safer to check `findActiveByAgentSession` before
|
|
62
|
+
// surfacing a typed error.
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
if (msg.includes('UNIQUE')) {
|
|
65
|
+
const existing = this.findActiveByAgentSession(input.agentSession);
|
|
66
|
+
if (existing) {
|
|
67
|
+
throw new ChatError(CHAT_ERROR_CODES.AGENT_ALREADY_BOUND, 409, `Agent "${input.agentSession}" is already bound to another active channel.`, { existingChannelId: existing.id });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
const created = this.getById(id);
|
|
73
|
+
// Row must exist — insert just succeeded. This branch is for type-narrowing.
|
|
74
|
+
if (!created) {
|
|
75
|
+
throw new ChatError(CHAT_ERROR_CODES.INTERNAL, 500, 'Channel disappeared immediately after insert');
|
|
76
|
+
}
|
|
77
|
+
return created;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Look up a channel by its id (active or archived).
|
|
81
|
+
*
|
|
82
|
+
* @param id - The channel id
|
|
83
|
+
* @returns The row, or null if not found
|
|
84
|
+
*/
|
|
85
|
+
getById(id) {
|
|
86
|
+
const row = this.db
|
|
87
|
+
.prepare(`SELECT ${CHANNEL_SELECT_COLUMNS}
|
|
88
|
+
FROM chat_channels
|
|
89
|
+
WHERE id = ?`)
|
|
90
|
+
.get(id);
|
|
91
|
+
return row ?? null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Find the single active channel bound to an agent session, if any.
|
|
95
|
+
* Uses the partial unique index, so at most one row is returned.
|
|
96
|
+
*
|
|
97
|
+
* @param agentSession - The agent session id
|
|
98
|
+
* @returns The active channel row, or null
|
|
99
|
+
*/
|
|
100
|
+
findActiveByAgentSession(agentSession) {
|
|
101
|
+
// Phase A: scope to type='dm' so this method's contract matches the
|
|
102
|
+
// post-migration `uq_channel_agent_dm_active` partial unique index.
|
|
103
|
+
// type='channel' rows can share the empty agent_session sentinel; we
|
|
104
|
+
// never want this lookup to surface those.
|
|
105
|
+
const row = this.db
|
|
106
|
+
.prepare(`SELECT ${CHANNEL_SELECT_COLUMNS}
|
|
107
|
+
FROM chat_channels
|
|
108
|
+
WHERE agent_session = ? AND archived_at IS NULL AND type = 'dm'
|
|
109
|
+
LIMIT 1`)
|
|
110
|
+
.get(agentSession);
|
|
111
|
+
return row ?? null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* List channels owned by a user.
|
|
115
|
+
*
|
|
116
|
+
* Phase C — extended with `type` + `teamId` filter options so the
|
|
117
|
+
* `GET /api/chat/channels` endpoint can serve the channel-rail's
|
|
118
|
+
* grouped/workspace-scoped views without shipping every row to the
|
|
119
|
+
* client. Filters compose with AND; passing `undefined` for either is
|
|
120
|
+
* a no-op (the existing all-channels behavior).
|
|
121
|
+
*
|
|
122
|
+
* @param ownerUserId - The user_id whose channels to return
|
|
123
|
+
* @param options - Listing options
|
|
124
|
+
* @param options.includeArchived - When false (default), filter out archived rows
|
|
125
|
+
* @param options.limit - Max rows to return (capped at 100)
|
|
126
|
+
* @param options.type - Phase C: when set, filter rows to this channel type
|
|
127
|
+
* (`'dm'` or `'channel'`). Useful for the channel-rail's "DMs only" /
|
|
128
|
+
* "Channels only" views.
|
|
129
|
+
* @param options.teamId - Phase C: when set, filter rows whose `team_id`
|
|
130
|
+
* matches. Used by the workspace-scoped Channels group; empty / null
|
|
131
|
+
* `team_id` rows are excluded by this filter on purpose (DMs and
|
|
132
|
+
* workspace-less rows belong to no team).
|
|
133
|
+
* @returns Channel rows sorted by `last_message_at DESC, created_at DESC`
|
|
134
|
+
*/
|
|
135
|
+
listByOwner(ownerUserId, options) {
|
|
136
|
+
const includeArchived = options?.includeArchived ?? false;
|
|
137
|
+
const limit = Math.min(options?.limit ?? 50, 100);
|
|
138
|
+
// Build WHERE clauses + bound params positionally so the filter set
|
|
139
|
+
// composes cleanly. Each branch is independent — no implicit coupling.
|
|
140
|
+
const where = ['owner_user_id = ?'];
|
|
141
|
+
const params = [ownerUserId];
|
|
142
|
+
if (!includeArchived) {
|
|
143
|
+
where.push('archived_at IS NULL');
|
|
144
|
+
}
|
|
145
|
+
if (options?.type !== undefined) {
|
|
146
|
+
where.push('type = ?');
|
|
147
|
+
params.push(options.type);
|
|
148
|
+
}
|
|
149
|
+
if (options?.teamId !== undefined) {
|
|
150
|
+
where.push('team_id = ?');
|
|
151
|
+
params.push(options.teamId);
|
|
152
|
+
}
|
|
153
|
+
const sql = `
|
|
154
|
+
SELECT ${CHANNEL_SELECT_COLUMNS}
|
|
155
|
+
FROM chat_channels
|
|
156
|
+
WHERE ${where.join(' AND ')}
|
|
157
|
+
ORDER BY COALESCE(last_message_at, created_at) DESC
|
|
158
|
+
LIMIT ?
|
|
159
|
+
`;
|
|
160
|
+
params.push(limit);
|
|
161
|
+
return this.db.prepare(sql).all(...params);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Mark a channel as archived (soft-delete). No-op if already archived.
|
|
165
|
+
*
|
|
166
|
+
* @param id - The channel id
|
|
167
|
+
* @param nowMs - Optional override for the archive timestamp
|
|
168
|
+
* @returns True if a row was updated (was active), false otherwise
|
|
169
|
+
*/
|
|
170
|
+
archive(id, nowMs) {
|
|
171
|
+
const result = this.db
|
|
172
|
+
.prepare(`UPDATE chat_channels
|
|
173
|
+
SET archived_at = ?
|
|
174
|
+
WHERE id = ? AND archived_at IS NULL`)
|
|
175
|
+
.run(nowMs ?? Date.now(), id);
|
|
176
|
+
return result.changes > 0;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Bump `last_message_at` to a provided timestamp if it is greater than
|
|
180
|
+
* the current value. Safe to call many times.
|
|
181
|
+
*
|
|
182
|
+
* @param id - The channel id
|
|
183
|
+
* @param timestampMs - The timestamp to record
|
|
184
|
+
*/
|
|
185
|
+
touchLastMessageAt(id, timestampMs) {
|
|
186
|
+
this.db
|
|
187
|
+
.prepare(`UPDATE chat_channels
|
|
188
|
+
SET last_message_at = ?
|
|
189
|
+
WHERE id = ?
|
|
190
|
+
AND (last_message_at IS NULL OR last_message_at < ?)`)
|
|
191
|
+
.run(timestampMs, id, timestampMs);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=channel.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.store.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/channel.store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat V2 SQLite bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Opens (or creates) the chat database at the configured path, applies
|
|
5
|
+
* the Phase 1 migration idempotently, and returns a ready-to-use
|
|
6
|
+
* `better-sqlite3` Database instance.
|
|
7
|
+
*
|
|
8
|
+
* Schema matches tech-spec §3.2. All tables use `INTEGER ms-since-epoch (UTC)`
|
|
9
|
+
* for timestamps and raw UUID strings for IDs.
|
|
10
|
+
*
|
|
11
|
+
* @module services/chat-v2/sqlite/chat-db
|
|
12
|
+
*/
|
|
13
|
+
import { type ComponentLogger } from '../../core/logger.service.js';
|
|
14
|
+
/** Type alias for a `better-sqlite3` Database instance. */
|
|
15
|
+
export type ChatDatabase = import('better-sqlite3').Database;
|
|
16
|
+
/**
|
|
17
|
+
* Idempotent base DDL — Phase 1 schema with Phase A column additions baked in.
|
|
18
|
+
*
|
|
19
|
+
* Safe to run every boot.
|
|
20
|
+
*
|
|
21
|
+
* Differences vs. the literal spec §3.2:
|
|
22
|
+
* - Added `IF NOT EXISTS` on every CREATE so reboots don't fail.
|
|
23
|
+
* - PRAGMAs set outside the transaction (SQLite disallows PRAGMA in txn).
|
|
24
|
+
*
|
|
25
|
+
* Phase A additions (SEALED design 2026-04-25 §3.1 + §3.2) are reflected
|
|
26
|
+
* directly in the CREATE TABLE bodies so a fresh-install database lands on
|
|
27
|
+
* the new schema in one shot. Pre-existing databases (created under the
|
|
28
|
+
* Phase 1 schema) get the same columns added by `applyPhaseAColumnUpgrades`
|
|
29
|
+
* below, which uses `ALTER TABLE ADD COLUMN` guarded by `PRAGMA table_info`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const CHAT_V2_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS chat_channels (\n id TEXT PRIMARY KEY,\n agent_session TEXT NOT NULL,\n owner_user_id TEXT NOT NULL,\n name TEXT NOT NULL,\n purpose TEXT,\n created_at INTEGER NOT NULL,\n archived_at INTEGER,\n last_message_at INTEGER,\n -- Phase A (SEALED \u00A73.1): channel-type discriminator. 'dm' preserves the\n -- Phase 1 1:1 user\u2194agent contract; 'channel' is the Slack-like team\n -- surface. Existing rows backfill to 'dm' via applyPhaseAColumnUpgrades.\n type TEXT NOT NULL DEFAULT 'dm' CHECK(type IN ('dm','channel')),\n -- Phase A (SEALED \u00A73.1): team workspace link. Required at the service\n -- layer when type='channel'; null for type='dm'.\n team_id TEXT,\n -- Phase A (SEALED \u00A73.1): optional project link for project-scoped channels.\n project_id TEXT,\n -- Phase A (SEALED \u00A73.1): for type='dm', the resolved member-ID being DM'd\n -- (distinct from agent_session which is the wire-level binding key).\n target_member_id TEXT\n);\n\nCREATE INDEX IF NOT EXISTS ix_channels_owner\n ON chat_channels(owner_user_id, archived_at);\n\n-- Phase A indexes that reference the new columns (type, team_id, thread_id)\n-- live in CHAT_V2_PHASE_A_INDEX_SQL below \u2014 they must be created AFTER\n-- applyPhaseAColumnUpgrades runs so the columns exist on legacy DBs.\n\nCREATE TABLE IF NOT EXISTS chat_messages (\n id TEXT PRIMARY KEY,\n channel_id TEXT NOT NULL REFERENCES chat_channels(id) ON DELETE CASCADE,\n seq INTEGER NOT NULL,\n sender_type TEXT NOT NULL CHECK(sender_type IN ('user','agent','system')),\n sender_id TEXT NOT NULL,\n content TEXT NOT NULL,\n content_type TEXT NOT NULL CHECK(content_type IN ('text','markdown','image_ref','system_note'))\n DEFAULT 'markdown',\n created_at INTEGER NOT NULL,\n metadata TEXT,\n -- Phase A (SEALED \u00A73.2): JSON-encoded array of mention IDs (member or\n -- team) referenced inline in the content field. Stored as a JSON string;\n -- service layer treats null as []. Bounded at insert time; see types.ts.\n mentions TEXT,\n -- Phase A (SEALED \u00A73.2): optional Slack-style thread root. When set,\n -- this message is a reply within the thread rooted at thread_id.\n thread_id TEXT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS uq_messages_channel_seq\n ON chat_messages(channel_id, seq);\n\nCREATE INDEX IF NOT EXISTS ix_messages_channel_created\n ON chat_messages(channel_id, created_at DESC);\n\n-- Phase 1: partial unique index for clientMessageId-based idempotency (spec \u00A74.4)\nCREATE UNIQUE INDEX IF NOT EXISTS uq_messages_client_id\n ON chat_messages(channel_id, json_extract(metadata, '$.clientMessageId'))\n WHERE json_extract(metadata, '$.clientMessageId') IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS chat_attachments (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,\n kind TEXT NOT NULL CHECK(kind IN ('image')),\n mime_type TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n local_path TEXT NOT NULL,\n original_name TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS ix_attachments_message\n ON chat_attachments(message_id);\n\nCREATE TABLE IF NOT EXISTS chat_offline_queue (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_id TEXT NOT NULL,\n agent_session TEXT NOT NULL,\n message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,\n queued_at INTEGER NOT NULL,\n delivered_at INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE INDEX IF NOT EXISTS ix_queue_pending\n ON chat_offline_queue(agent_session, delivered_at)\n WHERE delivered_at IS NULL;\n";
|
|
32
|
+
/**
|
|
33
|
+
* Phase A indexes that reference Phase A columns. Must run AFTER
|
|
34
|
+
* `applyPhaseAColumnUpgrades` adds those columns on pre-Phase-A databases —
|
|
35
|
+
* otherwise the partial-index `WHERE` clauses fail with "no such column".
|
|
36
|
+
*
|
|
37
|
+
* On a fresh database this runs after `CHAT_V2_MIGRATION_SQL` has already
|
|
38
|
+
* baked the columns into the CREATE TABLE bodies, so each step is a
|
|
39
|
+
* harmless no-op (`CREATE INDEX IF NOT EXISTS`).
|
|
40
|
+
*/
|
|
41
|
+
export declare const CHAT_V2_PHASE_A_INDEX_SQL = "\n-- Phase A (SEALED \u00A73.1): the 1:1 agent-binding only applies to type='dm'\n-- channels. type='channel' rows have agent_session='' and many such rows\n-- can coexist for the same team; the partial index intentionally excludes\n-- them so the unique constraint doesn't fire on multi-agent channels.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_channel_agent_dm_active\n ON chat_channels(agent_session)\n WHERE archived_at IS NULL AND type = 'dm';\n\n-- Phase A (SEALED \u00A73.1): scoped lookup for \"all channels in team T\" and\n-- \"project P channels\". Indexes by team first because team-scoped reads\n-- are the dominant Phase B+ access pattern.\nCREATE INDEX IF NOT EXISTS ix_channels_team\n ON chat_channels(team_id, archived_at)\n WHERE team_id IS NOT NULL;\n\n-- Phase A (SEALED \u00A73.2): thread-pane lookup. Filtered to non-null so the\n-- (smaller) index only covers actual threaded replies.\nCREATE INDEX IF NOT EXISTS ix_messages_thread\n ON chat_messages(thread_id, seq)\n WHERE thread_id IS NOT NULL;\n";
|
|
42
|
+
/**
|
|
43
|
+
* Bring a pre-existing chat database up to Phase A's schema by adding
|
|
44
|
+
* any missing columns and the new indexes / dropping the superseded ones.
|
|
45
|
+
*
|
|
46
|
+
* Safe to run on a fresh database too — every step is no-op idempotent.
|
|
47
|
+
* Specifically:
|
|
48
|
+
* - `ensureColumn` is a no-op when the column is already present (the
|
|
49
|
+
* fresh-install case, since the columns are baked into the CREATE
|
|
50
|
+
* TABLE in `CHAT_V2_MIGRATION_SQL`).
|
|
51
|
+
* - `DROP INDEX IF EXISTS uq_channel_agent_active` removes the legacy
|
|
52
|
+
* unconditional partial index so the new dm-scoped one isn't shadowed.
|
|
53
|
+
* The DROP is metadata-only — no row data is affected.
|
|
54
|
+
*
|
|
55
|
+
* Exported (separately from `openChatDatabase`) so tests can simulate the
|
|
56
|
+
* pre-Phase-A → Phase A migration path explicitly.
|
|
57
|
+
*
|
|
58
|
+
* @param db - The chat database handle
|
|
59
|
+
* @returns A small report describing what changed (for logging)
|
|
60
|
+
*/
|
|
61
|
+
export declare function applyPhaseAColumnUpgrades(db: ChatDatabase): {
|
|
62
|
+
channelsAdded: string[];
|
|
63
|
+
messagesAdded: string[];
|
|
64
|
+
legacyIndexDropped: boolean;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Options accepted by `openChatDatabase`.
|
|
68
|
+
*/
|
|
69
|
+
export interface OpenChatDatabaseOptions {
|
|
70
|
+
/** Absolute path to the SQLite file. Parent dirs will be created. */
|
|
71
|
+
dbPath: string;
|
|
72
|
+
/**
|
|
73
|
+
* When true, uses SQLite `:memory:` regardless of `dbPath`.
|
|
74
|
+
* Intended for unit tests. Defaults to false.
|
|
75
|
+
*/
|
|
76
|
+
inMemory?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* When true, suppress the integrity-check warning on open. Tests use this
|
|
79
|
+
* to avoid noisy logs. Defaults to false.
|
|
80
|
+
*/
|
|
81
|
+
skipIntegrityCheck?: boolean;
|
|
82
|
+
/** Optional logger override — defaults to LoggerService. */
|
|
83
|
+
logger?: ComponentLogger;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Open (or create) the chat database, apply PRAGMAs, run the Phase 1
|
|
87
|
+
* migration idempotently, and run a boot-time integrity check.
|
|
88
|
+
*
|
|
89
|
+
* @param options - Opener options
|
|
90
|
+
* @returns A ready-to-use better-sqlite3 Database handle
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const db = openChatDatabase({ dbPath: '/tmp/chat.db' });
|
|
95
|
+
* // ... use db ...
|
|
96
|
+
* db.close();
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function openChatDatabase(options: OpenChatDatabaseOptions): ChatDatabase;
|
|
100
|
+
//# sourceMappingURL=chat-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-db.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/chat-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AA+CnF,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,gBAAgB,EAAE,QAAQ,CAAC;AAM7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,ovHAyFjC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,qgCAqBrC,CAAC;AA2DF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG;IAC3D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CA0CA;AAMD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CAwD/E"}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat V2 SQLite bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Opens (or creates) the chat database at the configured path, applies
|
|
5
|
+
* the Phase 1 migration idempotently, and returns a ready-to-use
|
|
6
|
+
* `better-sqlite3` Database instance.
|
|
7
|
+
*
|
|
8
|
+
* Schema matches tech-spec §3.2. All tables use `INTEGER ms-since-epoch (UTC)`
|
|
9
|
+
* for timestamps and raw UUID strings for IDs.
|
|
10
|
+
*
|
|
11
|
+
* @module services/chat-v2/sqlite/chat-db
|
|
12
|
+
*/
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
import { pathToFileURL } from 'url';
|
|
17
|
+
import { LoggerService } from '../../core/logger.service.js';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Lazy better-sqlite3 loader — avoids a hard native-module dep at import time
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* CJS-style `require` scoped to this module. `better-sqlite3` is a native
|
|
23
|
+
* addon and must be loaded via CJS `require`, but this module compiles to
|
|
24
|
+
* ESM where the bare `require` global is undefined.
|
|
25
|
+
*
|
|
26
|
+
* Anchor `createRequire` to `process.argv[1]` (entry script) instead of
|
|
27
|
+
* `import.meta.url`. We previously used `new Function('return import.meta.url')()`
|
|
28
|
+
* to dodge ts-jest's TS1343 (CJS test compile rejects literal `import.meta`),
|
|
29
|
+
* but that trick fails at RUNTIME under ESM because `new Function(...)`
|
|
30
|
+
* evaluates in non-module scope where `import.meta` is a SyntaxError.
|
|
31
|
+
* `process.argv[1]` is always inside the project tree and lets Node's
|
|
32
|
+
* resolver walk up to find `node_modules`.
|
|
33
|
+
*/
|
|
34
|
+
const nodeRequire = typeof require === 'function'
|
|
35
|
+
? require
|
|
36
|
+
: createRequire(pathToFileURL(process.argv[1] || process.cwd()).href);
|
|
37
|
+
/** Cached reference to the better-sqlite3 module after first successful load. */
|
|
38
|
+
let _BetterSqlite3 = null;
|
|
39
|
+
/**
|
|
40
|
+
* Load `better-sqlite3` on demand. Throws a clear error if the native
|
|
41
|
+
* addon cannot be loaded (a common symptom after Node upgrades).
|
|
42
|
+
*
|
|
43
|
+
* @returns The lazily-imported better-sqlite3 module
|
|
44
|
+
*/
|
|
45
|
+
function getBetterSqlite3() {
|
|
46
|
+
if (!_BetterSqlite3) {
|
|
47
|
+
try {
|
|
48
|
+
_BetterSqlite3 = nodeRequire('better-sqlite3');
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
throw new Error('better-sqlite3 native module failed to load. Run `npm rebuild better-sqlite3` to fix. ' +
|
|
52
|
+
`Original error: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return _BetterSqlite3;
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Migration DDL (Phase 1 base + Phase A Slack-like extensions)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Idempotent base DDL — Phase 1 schema with Phase A column additions baked in.
|
|
62
|
+
*
|
|
63
|
+
* Safe to run every boot.
|
|
64
|
+
*
|
|
65
|
+
* Differences vs. the literal spec §3.2:
|
|
66
|
+
* - Added `IF NOT EXISTS` on every CREATE so reboots don't fail.
|
|
67
|
+
* - PRAGMAs set outside the transaction (SQLite disallows PRAGMA in txn).
|
|
68
|
+
*
|
|
69
|
+
* Phase A additions (SEALED design 2026-04-25 §3.1 + §3.2) are reflected
|
|
70
|
+
* directly in the CREATE TABLE bodies so a fresh-install database lands on
|
|
71
|
+
* the new schema in one shot. Pre-existing databases (created under the
|
|
72
|
+
* Phase 1 schema) get the same columns added by `applyPhaseAColumnUpgrades`
|
|
73
|
+
* below, which uses `ALTER TABLE ADD COLUMN` guarded by `PRAGMA table_info`.
|
|
74
|
+
*/
|
|
75
|
+
export const CHAT_V2_MIGRATION_SQL = `
|
|
76
|
+
CREATE TABLE IF NOT EXISTS chat_channels (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
agent_session TEXT NOT NULL,
|
|
79
|
+
owner_user_id TEXT NOT NULL,
|
|
80
|
+
name TEXT NOT NULL,
|
|
81
|
+
purpose TEXT,
|
|
82
|
+
created_at INTEGER NOT NULL,
|
|
83
|
+
archived_at INTEGER,
|
|
84
|
+
last_message_at INTEGER,
|
|
85
|
+
-- Phase A (SEALED §3.1): channel-type discriminator. 'dm' preserves the
|
|
86
|
+
-- Phase 1 1:1 user↔agent contract; 'channel' is the Slack-like team
|
|
87
|
+
-- surface. Existing rows backfill to 'dm' via applyPhaseAColumnUpgrades.
|
|
88
|
+
type TEXT NOT NULL DEFAULT 'dm' CHECK(type IN ('dm','channel')),
|
|
89
|
+
-- Phase A (SEALED §3.1): team workspace link. Required at the service
|
|
90
|
+
-- layer when type='channel'; null for type='dm'.
|
|
91
|
+
team_id TEXT,
|
|
92
|
+
-- Phase A (SEALED §3.1): optional project link for project-scoped channels.
|
|
93
|
+
project_id TEXT,
|
|
94
|
+
-- Phase A (SEALED §3.1): for type='dm', the resolved member-ID being DM'd
|
|
95
|
+
-- (distinct from agent_session which is the wire-level binding key).
|
|
96
|
+
target_member_id TEXT
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE INDEX IF NOT EXISTS ix_channels_owner
|
|
100
|
+
ON chat_channels(owner_user_id, archived_at);
|
|
101
|
+
|
|
102
|
+
-- Phase A indexes that reference the new columns (type, team_id, thread_id)
|
|
103
|
+
-- live in CHAT_V2_PHASE_A_INDEX_SQL below — they must be created AFTER
|
|
104
|
+
-- applyPhaseAColumnUpgrades runs so the columns exist on legacy DBs.
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
channel_id TEXT NOT NULL REFERENCES chat_channels(id) ON DELETE CASCADE,
|
|
109
|
+
seq INTEGER NOT NULL,
|
|
110
|
+
sender_type TEXT NOT NULL CHECK(sender_type IN ('user','agent','system')),
|
|
111
|
+
sender_id TEXT NOT NULL,
|
|
112
|
+
content TEXT NOT NULL,
|
|
113
|
+
content_type TEXT NOT NULL CHECK(content_type IN ('text','markdown','image_ref','system_note'))
|
|
114
|
+
DEFAULT 'markdown',
|
|
115
|
+
created_at INTEGER NOT NULL,
|
|
116
|
+
metadata TEXT,
|
|
117
|
+
-- Phase A (SEALED §3.2): JSON-encoded array of mention IDs (member or
|
|
118
|
+
-- team) referenced inline in the content field. Stored as a JSON string;
|
|
119
|
+
-- service layer treats null as []. Bounded at insert time; see types.ts.
|
|
120
|
+
mentions TEXT,
|
|
121
|
+
-- Phase A (SEALED §3.2): optional Slack-style thread root. When set,
|
|
122
|
+
-- this message is a reply within the thread rooted at thread_id.
|
|
123
|
+
thread_id TEXT
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_messages_channel_seq
|
|
127
|
+
ON chat_messages(channel_id, seq);
|
|
128
|
+
|
|
129
|
+
CREATE INDEX IF NOT EXISTS ix_messages_channel_created
|
|
130
|
+
ON chat_messages(channel_id, created_at DESC);
|
|
131
|
+
|
|
132
|
+
-- Phase 1: partial unique index for clientMessageId-based idempotency (spec §4.4)
|
|
133
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_messages_client_id
|
|
134
|
+
ON chat_messages(channel_id, json_extract(metadata, '$.clientMessageId'))
|
|
135
|
+
WHERE json_extract(metadata, '$.clientMessageId') IS NOT NULL;
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS chat_attachments (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,
|
|
140
|
+
kind TEXT NOT NULL CHECK(kind IN ('image')),
|
|
141
|
+
mime_type TEXT NOT NULL,
|
|
142
|
+
size_bytes INTEGER NOT NULL,
|
|
143
|
+
local_path TEXT NOT NULL,
|
|
144
|
+
original_name TEXT,
|
|
145
|
+
created_at INTEGER NOT NULL
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
CREATE INDEX IF NOT EXISTS ix_attachments_message
|
|
149
|
+
ON chat_attachments(message_id);
|
|
150
|
+
|
|
151
|
+
CREATE TABLE IF NOT EXISTS chat_offline_queue (
|
|
152
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
153
|
+
channel_id TEXT NOT NULL,
|
|
154
|
+
agent_session TEXT NOT NULL,
|
|
155
|
+
message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,
|
|
156
|
+
queued_at INTEGER NOT NULL,
|
|
157
|
+
delivered_at INTEGER,
|
|
158
|
+
attempts INTEGER NOT NULL DEFAULT 0
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
CREATE INDEX IF NOT EXISTS ix_queue_pending
|
|
162
|
+
ON chat_offline_queue(agent_session, delivered_at)
|
|
163
|
+
WHERE delivered_at IS NULL;
|
|
164
|
+
`;
|
|
165
|
+
/**
|
|
166
|
+
* Phase A indexes that reference Phase A columns. Must run AFTER
|
|
167
|
+
* `applyPhaseAColumnUpgrades` adds those columns on pre-Phase-A databases —
|
|
168
|
+
* otherwise the partial-index `WHERE` clauses fail with "no such column".
|
|
169
|
+
*
|
|
170
|
+
* On a fresh database this runs after `CHAT_V2_MIGRATION_SQL` has already
|
|
171
|
+
* baked the columns into the CREATE TABLE bodies, so each step is a
|
|
172
|
+
* harmless no-op (`CREATE INDEX IF NOT EXISTS`).
|
|
173
|
+
*/
|
|
174
|
+
export const CHAT_V2_PHASE_A_INDEX_SQL = `
|
|
175
|
+
-- Phase A (SEALED §3.1): the 1:1 agent-binding only applies to type='dm'
|
|
176
|
+
-- channels. type='channel' rows have agent_session='' and many such rows
|
|
177
|
+
-- can coexist for the same team; the partial index intentionally excludes
|
|
178
|
+
-- them so the unique constraint doesn't fire on multi-agent channels.
|
|
179
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_channel_agent_dm_active
|
|
180
|
+
ON chat_channels(agent_session)
|
|
181
|
+
WHERE archived_at IS NULL AND type = 'dm';
|
|
182
|
+
|
|
183
|
+
-- Phase A (SEALED §3.1): scoped lookup for "all channels in team T" and
|
|
184
|
+
-- "project P channels". Indexes by team first because team-scoped reads
|
|
185
|
+
-- are the dominant Phase B+ access pattern.
|
|
186
|
+
CREATE INDEX IF NOT EXISTS ix_channels_team
|
|
187
|
+
ON chat_channels(team_id, archived_at)
|
|
188
|
+
WHERE team_id IS NOT NULL;
|
|
189
|
+
|
|
190
|
+
-- Phase A (SEALED §3.2): thread-pane lookup. Filtered to non-null so the
|
|
191
|
+
-- (smaller) index only covers actual threaded replies.
|
|
192
|
+
CREATE INDEX IF NOT EXISTS ix_messages_thread
|
|
193
|
+
ON chat_messages(thread_id, seq)
|
|
194
|
+
WHERE thread_id IS NOT NULL;
|
|
195
|
+
`;
|
|
196
|
+
/** Phase A column additions for `chat_channels`. */
|
|
197
|
+
const CHAT_CHANNELS_PHASE_A_COLUMNS = [
|
|
198
|
+
// NOT NULL DEFAULT 'dm' lets ALTER TABLE backfill existing rows in one shot.
|
|
199
|
+
{ name: 'type', addClause: "type TEXT NOT NULL DEFAULT 'dm'" },
|
|
200
|
+
{ name: 'team_id', addClause: 'team_id TEXT' },
|
|
201
|
+
{ name: 'project_id', addClause: 'project_id TEXT' },
|
|
202
|
+
{ name: 'target_member_id', addClause: 'target_member_id TEXT' },
|
|
203
|
+
];
|
|
204
|
+
/** Phase A column additions for `chat_messages`. */
|
|
205
|
+
const CHAT_MESSAGES_PHASE_A_COLUMNS = [
|
|
206
|
+
{ name: 'mentions', addClause: 'mentions TEXT' },
|
|
207
|
+
{ name: 'thread_id', addClause: 'thread_id TEXT' },
|
|
208
|
+
];
|
|
209
|
+
/**
|
|
210
|
+
* Add a column to `table` if (and only if) it isn't already present.
|
|
211
|
+
*
|
|
212
|
+
* Uses `PRAGMA table_info(<table>)` to enumerate existing columns. SQLite
|
|
213
|
+
* does not have an `ADD COLUMN IF NOT EXISTS` form, so this guarded
|
|
214
|
+
* approach is the standard idempotent shape.
|
|
215
|
+
*
|
|
216
|
+
* @param db - The chat database handle
|
|
217
|
+
* @param table - Target table
|
|
218
|
+
* @param spec - Column to ensure
|
|
219
|
+
* @returns `true` if a column was added, `false` if it already existed
|
|
220
|
+
*/
|
|
221
|
+
function ensureColumn(db, table, spec) {
|
|
222
|
+
// PRAGMA cannot be parameterized; the table name is inlined. Callers
|
|
223
|
+
// supply only literal table names from the constants above — never
|
|
224
|
+
// user input — so SQL injection is not a concern here.
|
|
225
|
+
const cols = db.pragma(`table_info(${table})`);
|
|
226
|
+
if (cols.some((c) => c.name === spec.name)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${spec.addClause}`);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Bring a pre-existing chat database up to Phase A's schema by adding
|
|
234
|
+
* any missing columns and the new indexes / dropping the superseded ones.
|
|
235
|
+
*
|
|
236
|
+
* Safe to run on a fresh database too — every step is no-op idempotent.
|
|
237
|
+
* Specifically:
|
|
238
|
+
* - `ensureColumn` is a no-op when the column is already present (the
|
|
239
|
+
* fresh-install case, since the columns are baked into the CREATE
|
|
240
|
+
* TABLE in `CHAT_V2_MIGRATION_SQL`).
|
|
241
|
+
* - `DROP INDEX IF EXISTS uq_channel_agent_active` removes the legacy
|
|
242
|
+
* unconditional partial index so the new dm-scoped one isn't shadowed.
|
|
243
|
+
* The DROP is metadata-only — no row data is affected.
|
|
244
|
+
*
|
|
245
|
+
* Exported (separately from `openChatDatabase`) so tests can simulate the
|
|
246
|
+
* pre-Phase-A → Phase A migration path explicitly.
|
|
247
|
+
*
|
|
248
|
+
* @param db - The chat database handle
|
|
249
|
+
* @returns A small report describing what changed (for logging)
|
|
250
|
+
*/
|
|
251
|
+
export function applyPhaseAColumnUpgrades(db) {
|
|
252
|
+
const channelsAdded = [];
|
|
253
|
+
for (const spec of CHAT_CHANNELS_PHASE_A_COLUMNS) {
|
|
254
|
+
if (ensureColumn(db, 'chat_channels', spec)) {
|
|
255
|
+
channelsAdded.push(spec.name);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const messagesAdded = [];
|
|
259
|
+
for (const spec of CHAT_MESSAGES_PHASE_A_COLUMNS) {
|
|
260
|
+
if (ensureColumn(db, 'chat_messages', spec)) {
|
|
261
|
+
messagesAdded.push(spec.name);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Drop the legacy `uq_channel_agent_active` partial index — its constraint
|
|
265
|
+
// (`agent_session unique among archived_at IS NULL`) is too strict in the
|
|
266
|
+
// post-Phase-A world where multiple type='channel' rows legitimately share
|
|
267
|
+
// an empty agent_session. The replacement `uq_channel_agent_dm_active`
|
|
268
|
+
// (created below) carries the same semantics scoped to type='dm' rows only.
|
|
269
|
+
//
|
|
270
|
+
// The drop must happen BEFORE creating the new index — if the legacy
|
|
271
|
+
// index were left in place, an INSERT into a type='dm' row would still
|
|
272
|
+
// be checked by both indexes (same expression, different WHERE), which
|
|
273
|
+
// is harmless functionally but wastes write amplification.
|
|
274
|
+
const legacyIdx = db
|
|
275
|
+
.prepare(`SELECT name FROM sqlite_master
|
|
276
|
+
WHERE type = 'index' AND name = 'uq_channel_agent_active'`)
|
|
277
|
+
.get();
|
|
278
|
+
let legacyIndexDropped = false;
|
|
279
|
+
if (legacyIdx) {
|
|
280
|
+
db.exec('DROP INDEX IF EXISTS uq_channel_agent_active');
|
|
281
|
+
legacyIndexDropped = true;
|
|
282
|
+
}
|
|
283
|
+
// Now that all Phase A columns are guaranteed to exist, create the
|
|
284
|
+
// Phase A indexes that reference them. Idempotent — `CREATE INDEX IF NOT
|
|
285
|
+
// EXISTS` makes this a no-op on subsequent boots.
|
|
286
|
+
db.exec(CHAT_V2_PHASE_A_INDEX_SQL);
|
|
287
|
+
return { channelsAdded, messagesAdded, legacyIndexDropped };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Open (or create) the chat database, apply PRAGMAs, run the Phase 1
|
|
291
|
+
* migration idempotently, and run a boot-time integrity check.
|
|
292
|
+
*
|
|
293
|
+
* @param options - Opener options
|
|
294
|
+
* @returns A ready-to-use better-sqlite3 Database handle
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* const db = openChatDatabase({ dbPath: '/tmp/chat.db' });
|
|
299
|
+
* // ... use db ...
|
|
300
|
+
* db.close();
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
export function openChatDatabase(options) {
|
|
304
|
+
const logger = options.logger ??
|
|
305
|
+
LoggerService.getInstance().createComponentLogger('ChatV2Db');
|
|
306
|
+
if (!options.inMemory) {
|
|
307
|
+
const dir = path.dirname(options.dbPath);
|
|
308
|
+
if (dir && dir !== '.' && !existsSync(dir)) {
|
|
309
|
+
mkdirSync(dir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const Database = getBetterSqlite3();
|
|
313
|
+
const target = options.inMemory ? ':memory:' : options.dbPath;
|
|
314
|
+
const db = new Database(target);
|
|
315
|
+
// PRAGMAs — must be set outside any transaction (better-sqlite3 opens statements
|
|
316
|
+
// in autocommit mode by default, so a bare pragma() call is safe).
|
|
317
|
+
db.pragma('journal_mode = WAL');
|
|
318
|
+
db.pragma('foreign_keys = ON');
|
|
319
|
+
db.pragma('synchronous = NORMAL');
|
|
320
|
+
// Apply the Phase 1 migration idempotently. `exec` accepts multiple statements.
|
|
321
|
+
// For fresh databases this creates every table + index in one shot, including
|
|
322
|
+
// the Phase A columns baked into the CREATE TABLE bodies.
|
|
323
|
+
db.exec(CHAT_V2_MIGRATION_SQL);
|
|
324
|
+
// For pre-existing databases (created under Phase 1 schema), additively
|
|
325
|
+
// bring them up to Phase A: add missing columns and drop the superseded
|
|
326
|
+
// legacy `uq_channel_agent_active` index. Every step is no-op idempotent
|
|
327
|
+
// on a fresh DB.
|
|
328
|
+
const upgradeReport = applyPhaseAColumnUpgrades(db);
|
|
329
|
+
if (upgradeReport.channelsAdded.length > 0 ||
|
|
330
|
+
upgradeReport.messagesAdded.length > 0 ||
|
|
331
|
+
upgradeReport.legacyIndexDropped) {
|
|
332
|
+
logger.info('Chat DB Phase A schema upgrade applied', upgradeReport);
|
|
333
|
+
}
|
|
334
|
+
if (!options.skipIntegrityCheck) {
|
|
335
|
+
try {
|
|
336
|
+
const rows = db.pragma('integrity_check');
|
|
337
|
+
const first = rows[0]?.integrity_check;
|
|
338
|
+
if (first && first !== 'ok') {
|
|
339
|
+
logger.warn('Chat DB integrity check reported issues', { result: first });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
logger.warn('Chat DB integrity check threw — continuing without gating startup', {
|
|
344
|
+
error: err instanceof Error ? err.message : String(err),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
logger.info('Chat DB opened', { path: target });
|
|
349
|
+
return db;
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=chat-db.js.map
|