devchain-cli 0.10.5 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/cli.js +32 -28
- package/dist/drizzle/0048_sessions_transcript_fields.sql +6 -0
- package/dist/drizzle/0049_provider_models_and_agent_model_override.sql +24 -0
- package/dist/drizzle/meta/_journal.json +14 -0
- package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +30 -0
- package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
- package/dist/node_modules/@devchain/shared/schemas/export-schema.js +10 -3
- package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
- package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
- package/dist/server/app.main.module.js +2 -0
- package/dist/server/app.main.module.js.map +1 -1
- package/dist/server/app.normal.module.js +2 -0
- package/dist/server/app.normal.module.js.map +1 -1
- package/dist/server/modules/agents/controllers/agents.controller.d.ts +1 -0
- package/dist/server/modules/agents/controllers/agents.controller.js +3 -0
- package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
- package/dist/server/modules/chat/dtos/chat.dto.d.ts +4 -4
- package/dist/server/modules/core/services/preflight.service.js +7 -0
- package/dist/server/modules/core/services/preflight.service.js.map +1 -1
- package/dist/server/modules/core/services/provider-mcp-ensure.service.js +8 -0
- package/dist/server/modules/core/services/provider-mcp-ensure.service.js.map +1 -1
- package/dist/server/modules/documents/controllers/documents.controller.d.ts +2 -2
- package/dist/server/modules/documents/controllers/documents.controller.js.map +1 -1
- package/dist/server/modules/epics/controllers/epic-comments.controller.d.ts +2 -2
- package/dist/server/modules/epics/controllers/epic-comments.controller.js.map +1 -1
- package/dist/server/modules/events/catalog/claude.hooks.session.started.d.ts +4 -4
- package/dist/server/modules/events/catalog/index.d.ts +115 -4
- package/dist/server/modules/events/catalog/index.js +6 -0
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/session.transcript.discovered.d.ts +24 -0
- package/dist/server/modules/events/catalog/session.transcript.discovered.js +15 -0
- package/dist/server/modules/events/catalog/session.transcript.discovered.js.map +1 -0
- package/dist/server/modules/events/catalog/session.transcript.ended.d.ts +51 -0
- package/dist/server/modules/events/catalog/session.transcript.ended.js +20 -0
- package/dist/server/modules/events/catalog/session.transcript.ended.js.map +1 -0
- package/dist/server/modules/events/catalog/session.transcript.updated.d.ts +51 -0
- package/dist/server/modules/events/catalog/session.transcript.updated.js +20 -0
- package/dist/server/modules/events/catalog/session.transcript.updated.js.map +1 -0
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.d.ts +2 -2
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js.map +1 -1
- package/dist/server/modules/events/subscribers/index.js +2 -0
- package/dist/server/modules/events/subscribers/index.js.map +1 -1
- package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.d.ts +2 -2
- package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.js.map +1 -1
- package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.d.ts +12 -0
- package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.js +88 -0
- package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.js.map +1 -0
- package/dist/server/modules/guests/services/guest-health.service.d.ts +2 -2
- package/dist/server/modules/guests/services/guest-health.service.js.map +1 -1
- package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +4 -4
- package/dist/server/modules/hooks/services/hooks.service.d.ts +2 -2
- package/dist/server/modules/hooks/services/hooks.service.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.js +8 -3
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/services/handlers/activity-tools.d.ts +4 -0
- package/dist/server/modules/mcp/services/handlers/activity-tools.js +193 -0
- package/dist/server/modules/mcp/services/handlers/activity-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/chat-tools.d.ts +6 -0
- package/dist/server/modules/mcp/services/handlers/chat-tools.js +605 -0
- package/dist/server/modules/mcp/services/handlers/chat-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/document-tools.d.ts +6 -0
- package/dist/server/modules/mcp/services/handlers/document-tools.js +141 -0
- package/dist/server/modules/mcp/services/handlers/document-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +11 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.js +913 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/prompt-tools.d.ts +4 -0
- package/dist/server/modules/mcp/services/handlers/prompt-tools.js +109 -0
- package/dist/server/modules/mcp/services/handlers/prompt-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/record-tools.d.ts +8 -0
- package/dist/server/modules/mcp/services/handlers/record-tools.js +114 -0
- package/dist/server/modules/mcp/services/handlers/record-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/review-tools.d.ts +8 -0
- package/dist/server/modules/mcp/services/handlers/review-tools.js +672 -0
- package/dist/server/modules/mcp/services/handlers/review-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/session-tools.d.ts +4 -0
- package/dist/server/modules/mcp/services/handlers/session-tools.js +114 -0
- package/dist/server/modules/mcp/services/handlers/session-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/skill-tools.d.ts +4 -0
- package/dist/server/modules/mcp/services/handlers/skill-tools.js +124 -0
- package/dist/server/modules/mcp/services/handlers/skill-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/types.d.ts +33 -0
- package/dist/server/modules/mcp/services/handlers/types.js +3 -0
- package/dist/server/modules/mcp/services/handlers/types.js.map +1 -0
- package/dist/server/modules/mcp/services/mappers/dto-mappers.d.ts +14 -0
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js +138 -0
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js.map +1 -0
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.d.ts +8 -0
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js +258 -51
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +6 -66
- package/dist/server/modules/mcp/services/mcp.service.js +97 -3329
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/mcp/services/utils/document-link-resolver.d.ts +10 -0
- package/dist/server/modules/mcp/services/utils/document-link-resolver.js +124 -0
- package/dist/server/modules/mcp/services/utils/document-link-resolver.js.map +1 -0
- package/dist/server/modules/mcp/services/utils/resolve-epic-id.d.ts +3 -0
- package/dist/server/modules/mcp/services/utils/resolve-epic-id.js +40 -0
- package/dist/server/modules/mcp/services/utils/resolve-epic-id.js.map +1 -0
- package/dist/server/modules/mcp/services/utils/resource-resolver.d.ts +10 -0
- package/dist/server/modules/mcp/services/utils/resource-resolver.js +124 -0
- package/dist/server/modules/mcp/services/utils/resource-resolver.js.map +1 -0
- package/dist/server/modules/mcp/services/utils/session-context-resolver.d.ts +14 -0
- package/dist/server/modules/mcp/services/utils/session-context-resolver.js +169 -0
- package/dist/server/modules/mcp/services/utils/session-context-resolver.js.map +1 -0
- package/dist/server/modules/mcp/tool-definitions.js +12 -3
- package/dist/server/modules/mcp/tool-definitions.js.map +1 -1
- package/dist/server/modules/orchestrator/git/controllers/git.controller.d.ts +4 -2
- package/dist/server/modules/orchestrator/git/controllers/git.controller.js +44 -7
- package/dist/server/modules/orchestrator/git/controllers/git.controller.js.map +1 -1
- package/dist/server/modules/orchestrator/git/git.module.js +2 -0
- package/dist/server/modules/orchestrator/git/git.module.js.map +1 -1
- package/dist/server/modules/orchestrator/git/services/git-worktree.service.d.ts +6 -0
- package/dist/server/modules/orchestrator/git/services/git-worktree.service.js +55 -0
- package/dist/server/modules/orchestrator/git/services/git-worktree.service.js.map +1 -1
- package/dist/server/modules/orchestrator/ui/app/lib/worktrees.d.ts +17 -1
- package/dist/server/modules/orchestrator/ui/app/lib/worktrees.js +27 -2
- package/dist/server/modules/orchestrator/ui/app/lib/worktrees.js.map +1 -1
- package/dist/server/modules/orchestrator/ui/app/orchestrator-app.d.ts +2 -1
- package/dist/server/modules/orchestrator/ui/app/orchestrator-app.js +146 -8
- package/dist/server/modules/orchestrator/ui/app/orchestrator-app.js.map +1 -1
- package/dist/server/modules/orchestrator/worktrees/controllers/worktrees.controller.d.ts +6 -1
- package/dist/server/modules/orchestrator/worktrees/controllers/worktrees.controller.js +53 -3
- package/dist/server/modules/orchestrator/worktrees/controllers/worktrees.controller.js.map +1 -1
- package/dist/server/modules/orchestrator/worktrees/dtos/worktree.dto.d.ts +17 -4
- package/dist/server/modules/orchestrator/worktrees/dtos/worktree.dto.js +1 -1
- package/dist/server/modules/orchestrator/worktrees/dtos/worktree.dto.js.map +1 -1
- package/dist/server/modules/orchestrator/worktrees/services/worktrees.service.d.ts +7 -1
- package/dist/server/modules/orchestrator/worktrees/services/worktrees.service.js +130 -4
- package/dist/server/modules/orchestrator/worktrees/services/worktrees.service.js.map +1 -1
- package/dist/server/modules/orchestrator/worktrees/worktrees.module.js +2 -0
- package/dist/server/modules/orchestrator/worktrees/worktrees.module.js.map +1 -1
- package/dist/server/modules/profiles/controllers/provider-configs.controller.d.ts +2 -2
- package/dist/server/modules/profiles/controllers/provider-configs.controller.js.map +1 -1
- package/dist/server/modules/profiles/dto.d.ts +2 -2
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +34 -28
- package/dist/server/modules/projects/controllers/projects.controller.js +12 -7
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/dtos/export.dto.d.ts +7 -0
- package/dist/server/modules/projects/dtos/export.dto.js +1 -0
- package/dist/server/modules/projects/dtos/export.dto.js.map +1 -1
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.d.ts +102 -0
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.js +369 -0
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.js.map +1 -0
- package/dist/server/modules/projects/helpers/project-export.d.ts +142 -0
- package/dist/server/modules/projects/helpers/project-export.js +315 -0
- package/dist/server/modules/projects/helpers/project-export.js.map +1 -0
- package/dist/server/modules/projects/helpers/project-import.d.ts +132 -0
- package/dist/server/modules/projects/helpers/project-import.js +601 -0
- package/dist/server/modules/projects/helpers/project-import.js.map +1 -0
- package/dist/server/modules/projects/helpers/project-presets.helpers.d.ts +26 -0
- package/dist/server/modules/projects/helpers/project-presets.helpers.js +125 -0
- package/dist/server/modules/projects/helpers/project-presets.helpers.js.map +1 -0
- package/dist/server/modules/projects/helpers/project-runtime.helpers.d.ts +69 -0
- package/dist/server/modules/projects/helpers/project-runtime.helpers.js +201 -0
- package/dist/server/modules/projects/helpers/project-runtime.helpers.js.map +1 -0
- package/dist/server/modules/projects/helpers/project-template-manifest.helpers.d.ts +16 -0
- package/dist/server/modules/projects/helpers/project-template-manifest.helpers.js +107 -0
- package/dist/server/modules/projects/helpers/project-template-manifest.helpers.js.map +1 -0
- package/dist/server/modules/projects/helpers/template-file.helpers.d.ts +9 -0
- package/dist/server/modules/projects/helpers/template-file.helpers.js +92 -0
- package/dist/server/modules/projects/helpers/template-file.helpers.js.map +1 -0
- package/dist/server/modules/projects/helpers/template-loader.d.ts +85 -0
- package/dist/server/modules/projects/helpers/template-loader.js +407 -0
- package/dist/server/modules/projects/helpers/template-loader.js.map +1 -0
- package/dist/server/modules/projects/services/main-project-bootstrap.service.js +4 -0
- package/dist/server/modules/projects/services/main-project-bootstrap.service.js.map +1 -1
- package/dist/server/modules/projects/services/projects.service.d.ts +24 -49
- package/dist/server/modules/projects/services/projects.service.js +46 -1848
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/prompts/controllers/prompts.controller.d.ts +2 -2
- package/dist/server/modules/prompts/controllers/prompts.controller.js.map +1 -1
- package/dist/server/modules/providers/adapters/index.d.ts +1 -0
- package/dist/server/modules/providers/adapters/index.js +1 -0
- package/dist/server/modules/providers/adapters/index.js.map +1 -1
- package/dist/server/modules/providers/adapters/opencode.adapter.d.ts +16 -0
- package/dist/server/modules/providers/adapters/opencode.adapter.js +64 -0
- package/dist/server/modules/providers/adapters/opencode.adapter.js.map +1 -0
- package/dist/server/modules/providers/adapters/provider-adapter.factory.d.ts +2 -1
- package/dist/server/modules/providers/adapters/provider-adapter.factory.js +5 -2
- package/dist/server/modules/providers/adapters/provider-adapter.factory.js.map +1 -1
- package/dist/server/modules/providers/adapters/provider-adapter.interface.d.ts +1 -0
- package/dist/server/modules/providers/adapters/provider-adapters.module.js +2 -1
- package/dist/server/modules/providers/adapters/provider-adapters.module.js.map +1 -1
- package/dist/server/modules/providers/controllers/provider-models.controller.d.ts +22 -0
- package/dist/server/modules/providers/controllers/provider-models.controller.js +173 -0
- package/dist/server/modules/providers/controllers/provider-models.controller.js.map +1 -0
- package/dist/server/modules/providers/providers.module.js +2 -1
- package/dist/server/modules/providers/providers.module.js.map +1 -1
- package/dist/server/modules/records/controllers/records.controller.d.ts +2 -2
- package/dist/server/modules/records/controllers/records.controller.js.map +1 -1
- package/dist/server/modules/registry/services/template-upgrade.service.js +1 -9
- package/dist/server/modules/registry/services/template-upgrade.service.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +21 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +207 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.d.ts +22 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js +214 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.d.ts +23 -0
- package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.js +224 -0
- package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.d.ts +9 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.js +40 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +37 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js +3 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.d.ts +11 -0
- package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.js +77 -0
- package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.js.map +1 -0
- package/dist/server/modules/session-reader/adapters/utils/file-search.util.d.ts +1 -0
- package/dist/server/modules/session-reader/adapters/utils/file-search.util.js +22 -0
- package/dist/server/modules/session-reader/adapters/utils/file-search.util.js.map +1 -0
- package/dist/server/modules/session-reader/builders/chunk-builder.d.ts +5 -0
- package/dist/server/modules/session-reader/builders/chunk-builder.js +131 -0
- package/dist/server/modules/session-reader/builders/chunk-builder.js.map +1 -0
- package/dist/server/modules/session-reader/builders/semantic-step-extractor.d.ts +3 -0
- package/dist/server/modules/session-reader/builders/semantic-step-extractor.js +93 -0
- package/dist/server/modules/session-reader/builders/semantic-step-extractor.js.map +1 -0
- package/dist/server/modules/session-reader/builders/turn-builder.d.ts +3 -0
- package/dist/server/modules/session-reader/builders/turn-builder.js +102 -0
- package/dist/server/modules/session-reader/builders/turn-builder.js.map +1 -0
- package/dist/server/modules/session-reader/controllers/session-reader.controller.d.ts +430 -0
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js +252 -0
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -0
- package/dist/server/modules/session-reader/data/pricing.json +5113 -0
- package/dist/server/modules/session-reader/dtos/index.d.ts +2 -0
- package/dist/server/modules/session-reader/dtos/index.js +10 -0
- package/dist/server/modules/session-reader/dtos/index.js.map +1 -0
- package/dist/server/modules/session-reader/dtos/unified-chunk.types.d.ts +92 -0
- package/dist/server/modules/session-reader/dtos/unified-chunk.types.js +19 -0
- package/dist/server/modules/session-reader/dtos/unified-chunk.types.js.map +1 -0
- package/dist/server/modules/session-reader/dtos/unified-session.types.d.ts +118 -0
- package/dist/server/modules/session-reader/dtos/unified-session.types.js +23 -0
- package/dist/server/modules/session-reader/dtos/unified-session.types.js.map +1 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +15 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +363 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -0
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.d.ts +16 -0
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +622 -0
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -0
- package/dist/server/modules/session-reader/parsers/gemini-json.parser.d.ts +15 -0
- package/dist/server/modules/session-reader/parsers/gemini-json.parser.js +380 -0
- package/dist/server/modules/session-reader/parsers/gemini-json.parser.js.map +1 -0
- package/dist/server/modules/session-reader/services/pricing.interface.d.ts +9 -0
- package/dist/server/modules/session-reader/services/pricing.interface.js +24 -0
- package/dist/server/modules/session-reader/services/pricing.interface.js.map +1 -0
- package/dist/server/modules/session-reader/services/pricing.service.d.ts +21 -0
- package/dist/server/modules/session-reader/services/pricing.service.js +106 -0
- package/dist/server/modules/session-reader/services/pricing.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/session-cache.service.d.ts +24 -0
- package/dist/server/modules/session-reader/services/session-cache.service.js +203 -0
- package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/session-reader.service.d.ts +56 -0
- package/dist/server/modules/session-reader/services/session-reader.service.js +305 -0
- package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/subagent-locator.service.d.ts +10 -0
- package/dist/server/modules/session-reader/services/subagent-locator.service.js +102 -0
- package/dist/server/modules/session-reader/services/subagent-locator.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/subagent-resolver.service.d.ts +22 -0
- package/dist/server/modules/session-reader/services/subagent-resolver.service.js +211 -0
- package/dist/server/modules/session-reader/services/subagent-resolver.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/transcript-path-validator.service.d.ts +7 -0
- package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +150 -0
- package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -0
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.d.ts +30 -0
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js +336 -0
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js.map +1 -0
- package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +28 -0
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js +325 -0
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -0
- package/dist/server/modules/session-reader/session-reader.module.d.ts +13 -0
- package/dist/server/modules/session-reader/session-reader.module.js +82 -0
- package/dist/server/modules/session-reader/session-reader.module.js.map +1 -0
- package/dist/server/modules/sessions/dtos/sessions.dto.d.ts +2 -0
- package/dist/server/modules/sessions/dtos/sessions.dto.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions-message-pool.service.d.ts +2 -2
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions.service.js +19 -4
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/utils/profile-options.d.ts +1 -0
- package/dist/server/modules/sessions/utils/profile-options.js +18 -0
- package/dist/server/modules/sessions/utils/profile-options.js.map +1 -1
- package/dist/server/modules/settings/controllers/settings.controller.d.ts +2 -2
- package/dist/server/modules/settings/controllers/settings.controller.js.map +1 -1
- package/dist/server/modules/settings/dtos/settings.dto.d.ts +14 -2
- package/dist/server/modules/settings/dtos/settings.dto.js +1 -0
- package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
- package/dist/server/modules/skills/services/skill-source-registry.service.d.ts +2 -2
- package/dist/server/modules/skills/services/skill-source-registry.service.js.map +1 -1
- package/dist/server/modules/statuses/controllers/statuses.controller.d.ts +2 -2
- package/dist/server/modules/statuses/controllers/statuses.controller.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +176 -0
- package/dist/server/modules/storage/db/schema.js +17 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +54 -9
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/agent-profile.delegate.d.ts +39 -0
- package/dist/server/modules/storage/local/delegates/agent-profile.delegate.js +286 -0
- package/dist/server/modules/storage/local/delegates/agent-profile.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/agent.delegate.d.ts +21 -0
- package/dist/server/modules/storage/local/delegates/agent.delegate.js +202 -0
- package/dist/server/modules/storage/local/delegates/agent.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/base-storage.delegate.d.ts +12 -0
- package/dist/server/modules/storage/local/delegates/base-storage.delegate.js +19 -0
- package/dist/server/modules/storage/local/delegates/base-storage.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/document.delegate.d.ts +20 -0
- package/dist/server/modules/storage/local/delegates/document.delegate.js +267 -0
- package/dist/server/modules/storage/local/delegates/document.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +38 -0
- package/dist/server/modules/storage/local/delegates/epic.delegate.js +686 -0
- package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/guest.delegate.d.ts +14 -0
- package/dist/server/modules/storage/local/delegates/guest.delegate.js +172 -0
- package/dist/server/modules/storage/local/delegates/guest.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.d.ts +17 -0
- package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.js +249 -0
- package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +16 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.js +631 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/prompt.delegate.d.ts +17 -0
- package/dist/server/modules/storage/local/delegates/prompt.delegate.js +318 -0
- package/dist/server/modules/storage/local/delegates/prompt.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/provider-model.delegate.d.ts +15 -0
- package/dist/server/modules/storage/local/delegates/provider-model.delegate.js +210 -0
- package/dist/server/modules/storage/local/delegates/provider-model.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/provider.delegate.d.ts +18 -0
- package/dist/server/modules/storage/local/delegates/provider.delegate.js +151 -0
- package/dist/server/modules/storage/local/delegates/provider.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/record.delegate.d.ts +16 -0
- package/dist/server/modules/storage/local/delegates/record.delegate.js +190 -0
- package/dist/server/modules/storage/local/delegates/record.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/review.delegate.d.ts +25 -0
- package/dist/server/modules/storage/local/delegates/review.delegate.js +426 -0
- package/dist/server/modules/storage/local/delegates/review.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/skill-source.delegate.d.ts +28 -0
- package/dist/server/modules/storage/local/delegates/skill-source.delegate.js +347 -0
- package/dist/server/modules/storage/local/delegates/skill-source.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/status.delegate.d.ts +12 -0
- package/dist/server/modules/storage/local/delegates/status.delegate.js +130 -0
- package/dist/server/modules/storage/local/delegates/status.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/subscriber.delegate.d.ts +11 -0
- package/dist/server/modules/storage/local/delegates/subscriber.delegate.js +144 -0
- package/dist/server/modules/storage/local/delegates/subscriber.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/tag.delegate.d.ts +11 -0
- package/dist/server/modules/storage/local/delegates/tag.delegate.js +102 -0
- package/dist/server/modules/storage/local/delegates/tag.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/watcher.delegate.d.ts +12 -0
- package/dist/server/modules/storage/local/delegates/watcher.delegate.js +183 -0
- package/dist/server/modules/storage/local/delegates/watcher.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/helpers/storage-helpers.d.ts +32 -0
- package/dist/server/modules/storage/local/helpers/storage-helpers.js +276 -0
- package/dist/server/modules/storage/local/helpers/storage-helpers.js.map +1 -0
- package/dist/server/modules/storage/local/local-storage.service.d.ts +30 -22
- package/dist/server/modules/storage/local/local-storage.service.js +227 -3627
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +14 -1
- package/dist/server/modules/subscribers/services/subscribers.service.d.ts +2 -2
- package/dist/server/modules/subscribers/services/subscribers.service.js.map +1 -1
- package/dist/server/modules/watchers/services/watchers.service.d.ts +2 -2
- package/dist/server/templates/3-agents-dev.json +180 -162
- package/dist/server/templates/5-agents-dev.json +244 -194
- package/dist/server/test-setup-node.js +13 -0
- package/dist/server/test-setup-node.js.map +1 -1
- package/dist/server/test-setup.js +14 -12
- package/dist/server/test-setup.js.map +1 -1
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-DNFeBzB6.js → ReviewDetailPage-BFLK1NAe.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-DCM_dJlA.js → ReviewsPage-BqF2XSr0.js} +1 -1
- package/dist/server/ui/assets/index-Bs5Kat1T.js +1011 -0
- package/dist/server/ui/assets/index-CW3WYriQ.css +32 -0
- package/dist/server/ui/assets/{useReviewSubscription-Bx6D-cpf.js → useReviewSubscription-B73OxTf6.js} +32 -35
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/3-agents-dev.json +180 -162
- package/dist/templates/5-agents-dev.json +244 -194
- package/package.json +23 -2
- package/dist/server/ui/assets/index-BgvrDIBb.js +0 -972
- package/dist/server/ui/assets/index-CzjcxP26.css +0 -32
|
@@ -51,132 +51,91 @@ const better_sqlite3_1 = require("drizzle-orm/better-sqlite3");
|
|
|
51
51
|
const db_provider_1 = require("../db/db.provider");
|
|
52
52
|
const error_types_1 = require("../../../common/errors/error-types");
|
|
53
53
|
const logger_1 = require("../../../common/logging/logger");
|
|
54
|
-
const sqlite_raw_1 = require("../db/sqlite-raw");
|
|
55
54
|
const feature_flags_1 = require("../../../common/config/feature-flags");
|
|
55
|
+
const storage_helpers_1 = require("./helpers/storage-helpers");
|
|
56
|
+
const base_storage_delegate_1 = require("./delegates/base-storage.delegate");
|
|
57
|
+
const agent_delegate_1 = require("./delegates/agent.delegate");
|
|
58
|
+
const agent_profile_delegate_1 = require("./delegates/agent-profile.delegate");
|
|
59
|
+
const document_delegate_1 = require("./delegates/document.delegate");
|
|
60
|
+
const epic_delegate_1 = require("./delegates/epic.delegate");
|
|
61
|
+
const guest_delegate_1 = require("./delegates/guest.delegate");
|
|
62
|
+
const profile_provider_config_delegate_1 = require("./delegates/profile-provider-config.delegate");
|
|
63
|
+
const prompt_delegate_1 = require("./delegates/prompt.delegate");
|
|
64
|
+
const provider_delegate_1 = require("./delegates/provider.delegate");
|
|
65
|
+
const provider_model_delegate_1 = require("./delegates/provider-model.delegate");
|
|
66
|
+
const project_delegate_1 = require("./delegates/project.delegate");
|
|
67
|
+
const record_delegate_1 = require("./delegates/record.delegate");
|
|
68
|
+
const review_delegate_1 = require("./delegates/review.delegate");
|
|
69
|
+
const skill_source_delegate_1 = require("./delegates/skill-source.delegate");
|
|
70
|
+
const status_delegate_1 = require("./delegates/status.delegate");
|
|
71
|
+
const subscriber_delegate_1 = require("./delegates/subscriber.delegate");
|
|
72
|
+
const tag_delegate_1 = require("./delegates/tag.delegate");
|
|
73
|
+
const watcher_delegate_1 = require("./delegates/watcher.delegate");
|
|
56
74
|
const logger = (0, logger_1.createLogger)('LocalStorageService');
|
|
57
|
-
const COMMUNITY_SOURCE_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
58
|
-
const RESERVED_COMMUNITY_SOURCE_NAMES = new Set([
|
|
59
|
-
'anthropic',
|
|
60
|
-
'openai',
|
|
61
|
-
'microsoft',
|
|
62
|
-
'trailofbits',
|
|
63
|
-
'vercel',
|
|
64
|
-
]);
|
|
65
75
|
let LocalStorageService = class LocalStorageService {
|
|
66
76
|
constructor(db) {
|
|
67
77
|
this.db = db;
|
|
78
|
+
const context = (0, base_storage_delegate_1.createStorageDelegateContext)(this.db);
|
|
79
|
+
this.projectDelegate = new project_delegate_1.ProjectStorageDelegate(context);
|
|
80
|
+
this.statusDelegate = new status_delegate_1.StatusStorageDelegate(context);
|
|
81
|
+
this.tagDelegate = new tag_delegate_1.TagStorageDelegate(context);
|
|
82
|
+
this.promptDelegate = new prompt_delegate_1.PromptStorageDelegate(context, {
|
|
83
|
+
createTag: (data) => this.createTag(data),
|
|
84
|
+
getPrompt: (id) => this.getPrompt(id),
|
|
85
|
+
});
|
|
86
|
+
this.documentDelegate = new document_delegate_1.DocumentStorageDelegate(context, {
|
|
87
|
+
createTag: (data) => this.createTag(data),
|
|
88
|
+
getDocument: (identifier) => this.getDocument(identifier),
|
|
89
|
+
generateDocumentSlug: (projectId, desired, excludeId) => excludeId === undefined
|
|
90
|
+
? this.generateDocumentSlug(projectId, desired)
|
|
91
|
+
: this.generateDocumentSlug(projectId, desired, excludeId),
|
|
92
|
+
setDocumentTags: (documentId, tagNames, projectId) => this.setDocumentTags(documentId, tagNames, projectId),
|
|
93
|
+
});
|
|
94
|
+
this.epicDelegate = new epic_delegate_1.EpicStorageDelegate(context, {
|
|
95
|
+
createTag: (data) => this.createTag(data),
|
|
96
|
+
getAgent: (id) => this.getAgent(id),
|
|
97
|
+
getAgentByName: (projectId, name) => this.getAgentByName(projectId, name),
|
|
98
|
+
getStatus: (id) => this.statusDelegate.getStatus(id),
|
|
99
|
+
});
|
|
100
|
+
this.providerDelegate = new provider_delegate_1.ProviderStorageDelegate(context, {
|
|
101
|
+
updateProvider: (id, data) => this.updateProvider(id, data),
|
|
102
|
+
});
|
|
103
|
+
this.providerModelDelegate = new provider_model_delegate_1.ProviderModelStorageDelegate(context);
|
|
104
|
+
this.skillSourceDelegate = new skill_source_delegate_1.SkillSourceStorageDelegate(context, {
|
|
105
|
+
assertLocalSourceNameAvailableAcrossTypes: (sourceName) => this.assertLocalSourceNameAvailableAcrossTypes(sourceName),
|
|
106
|
+
getCommunitySkillSource: (id) => this.getCommunitySkillSource(id),
|
|
107
|
+
getLocalSkillSource: (id) => this.getLocalSkillSource(id),
|
|
108
|
+
});
|
|
109
|
+
this.agentProfileDelegate = new agent_profile_delegate_1.AgentProfileStorageDelegate(context, {
|
|
110
|
+
getAgentProfile: (id) => this.getAgentProfile(id),
|
|
111
|
+
listAgentProfiles: (options) => this.listAgentProfiles(options),
|
|
112
|
+
});
|
|
113
|
+
this.profileProviderConfigDelegate = new profile_provider_config_delegate_1.ProfileProviderConfigStorageDelegate(context, {
|
|
114
|
+
getProfileProviderConfig: (id) => this.getProfileProviderConfig(id),
|
|
115
|
+
});
|
|
116
|
+
this.agentDelegate = new agent_delegate_1.AgentStorageDelegate(context, {
|
|
117
|
+
getAgent: (id) => this.getAgent(id),
|
|
118
|
+
getAgentProfile: (id) => this.getAgentProfile(id),
|
|
119
|
+
getProfileProviderConfig: (id) => this.getProfileProviderConfig(id),
|
|
120
|
+
});
|
|
121
|
+
this.recordDelegate = new record_delegate_1.RecordStorageDelegate(context, {
|
|
122
|
+
createTag: (data) => this.createTag(data),
|
|
123
|
+
getRecord: (id) => this.getRecord(id),
|
|
124
|
+
});
|
|
125
|
+
this.watcherDelegate = new watcher_delegate_1.WatcherStorageDelegate(context);
|
|
126
|
+
this.subscriberDelegate = new subscriber_delegate_1.SubscriberStorageDelegate(context);
|
|
127
|
+
this.guestDelegate = new guest_delegate_1.GuestStorageDelegate(context);
|
|
128
|
+
this.reviewDelegate = new review_delegate_1.ReviewStorageDelegate(context, {
|
|
129
|
+
getReview: (id) => this.getReview(id),
|
|
130
|
+
getReviewComment: (id) => this.getReviewComment(id),
|
|
131
|
+
});
|
|
68
132
|
logger.info('LocalStorageService initialized');
|
|
69
133
|
}
|
|
70
134
|
getFeatureFlags() {
|
|
71
135
|
return { ...feature_flags_1.DEFAULT_FEATURE_FLAGS };
|
|
72
136
|
}
|
|
73
|
-
parseProviderConfigEnv(envJson, configId, profileId) {
|
|
74
|
-
if (!envJson) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const parsed = JSON.parse(envJson);
|
|
79
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
80
|
-
throw new Error('env must be an object');
|
|
81
|
-
}
|
|
82
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
83
|
-
if (typeof value !== 'string') {
|
|
84
|
-
throw new Error(`env["${key}"] must be a string, got ${typeof value}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return parsed;
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
-
logger.warn({ configId, profileId, error: message }, 'Failed to parse provider config env JSON');
|
|
92
|
-
throw new error_types_1.ValidationError(`Invalid JSON in provider config env field: ${message}`, {
|
|
93
|
-
configId,
|
|
94
|
-
profileId,
|
|
95
|
-
rawValue: envJson.slice(0, 100) + (envJson.length > 100 ? '...' : ''),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
parseSkillsRequired(raw) {
|
|
100
|
-
if (raw === null || raw === undefined) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
if (Array.isArray(raw)) {
|
|
104
|
-
if (raw.every((value) => typeof value === 'string')) {
|
|
105
|
-
return raw;
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
if (typeof raw !== 'string') {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
const trimmed = raw.trim();
|
|
113
|
-
if (!trimmed) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const parsed = JSON.parse(trimmed);
|
|
118
|
-
return Array.isArray(parsed) && parsed.every((value) => typeof value === 'string')
|
|
119
|
-
? parsed
|
|
120
|
-
: null;
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
serializeSkillsRequired(skillsRequired) {
|
|
127
|
-
if (skillsRequired === null || skillsRequired === undefined) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
return JSON.stringify(skillsRequired);
|
|
131
|
-
}
|
|
132
|
-
normalizeCommunitySourceName(name) {
|
|
133
|
-
const normalized = name.trim().toLowerCase();
|
|
134
|
-
if (!normalized) {
|
|
135
|
-
throw new error_types_1.ValidationError('name is required.', { fieldName: 'name' });
|
|
136
|
-
}
|
|
137
|
-
if (!COMMUNITY_SOURCE_NAME_PATTERN.test(normalized)) {
|
|
138
|
-
throw new error_types_1.ValidationError('Invalid community source name. Use lowercase letters, numbers, and hyphens only.', { name: normalized });
|
|
139
|
-
}
|
|
140
|
-
if (RESERVED_COMMUNITY_SOURCE_NAMES.has(normalized)) {
|
|
141
|
-
throw new error_types_1.ValidationError('Community source name conflicts with a built-in source.', {
|
|
142
|
-
name: normalized,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return normalized;
|
|
146
|
-
}
|
|
147
|
-
normalizeCommunitySourceNameForLookup(name) {
|
|
148
|
-
const normalized = name.trim().toLowerCase();
|
|
149
|
-
if (!normalized) {
|
|
150
|
-
throw new error_types_1.ValidationError('name is required.', { fieldName: 'name' });
|
|
151
|
-
}
|
|
152
|
-
if (!COMMUNITY_SOURCE_NAME_PATTERN.test(normalized)) {
|
|
153
|
-
throw new error_types_1.ValidationError('Invalid community source name. Use lowercase letters, numbers, and hyphens only.', { name: normalized });
|
|
154
|
-
}
|
|
155
|
-
return normalized;
|
|
156
|
-
}
|
|
157
|
-
normalizeCommunityRepoPart(value, fieldName) {
|
|
158
|
-
const normalized = value.trim().toLowerCase();
|
|
159
|
-
if (!normalized) {
|
|
160
|
-
throw new error_types_1.ValidationError(`${fieldName} is required.`, { fieldName });
|
|
161
|
-
}
|
|
162
|
-
return normalized;
|
|
163
|
-
}
|
|
164
|
-
normalizeCommunityBranch(branch) {
|
|
165
|
-
const normalized = (branch ?? 'main').trim();
|
|
166
|
-
if (!normalized) {
|
|
167
|
-
throw new error_types_1.ValidationError('branch is required.', { fieldName: 'branch' });
|
|
168
|
-
}
|
|
169
|
-
return normalized;
|
|
170
|
-
}
|
|
171
|
-
normalizeLocalSkillSourceFolderPath(folderPath) {
|
|
172
|
-
const normalized = folderPath.trim();
|
|
173
|
-
if (!normalized) {
|
|
174
|
-
throw new error_types_1.ValidationError('folderPath is required.', { fieldName: 'folderPath' });
|
|
175
|
-
}
|
|
176
|
-
return normalized;
|
|
177
|
-
}
|
|
178
137
|
async assertLocalSourceNameAvailableAcrossTypes(sourceName) {
|
|
179
|
-
if (RESERVED_COMMUNITY_SOURCE_NAMES.has(sourceName)) {
|
|
138
|
+
if (storage_helpers_1.RESERVED_COMMUNITY_SOURCE_NAMES.has(sourceName)) {
|
|
180
139
|
throw new error_types_1.ValidationError('Local source name conflicts with a built-in source.', {
|
|
181
140
|
name: sourceName,
|
|
182
141
|
});
|
|
@@ -194,3773 +153,428 @@ let LocalStorageService = class LocalStorageService {
|
|
|
194
153
|
});
|
|
195
154
|
}
|
|
196
155
|
}
|
|
197
|
-
normalizeProjectIdForSourceEnablement(projectId) {
|
|
198
|
-
const normalized = projectId.trim();
|
|
199
|
-
if (!normalized) {
|
|
200
|
-
throw new error_types_1.ValidationError('projectId is required.', { fieldName: 'projectId' });
|
|
201
|
-
}
|
|
202
|
-
return normalized;
|
|
203
|
-
}
|
|
204
|
-
normalizeSourceNameForSourceEnablement(sourceName) {
|
|
205
|
-
const normalized = sourceName.trim().toLowerCase();
|
|
206
|
-
if (!normalized) {
|
|
207
|
-
throw new error_types_1.ValidationError('sourceName is required.', { fieldName: 'sourceName' });
|
|
208
|
-
}
|
|
209
|
-
return normalized;
|
|
210
|
-
}
|
|
211
|
-
isSqliteUniqueConstraint(error) {
|
|
212
|
-
if (typeof error !== 'object' || error === null) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
const code = 'code' in error ? error.code : undefined;
|
|
216
|
-
const message = 'message' in error ? error.message : undefined;
|
|
217
|
-
const normalizedMessage = typeof message === 'string' ? message : '';
|
|
218
|
-
return (code === 'SQLITE_CONSTRAINT' ||
|
|
219
|
-
code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
|
220
|
-
code === 19 ||
|
|
221
|
-
normalizedMessage.includes('UNIQUE constraint failed'));
|
|
222
|
-
}
|
|
223
|
-
async ensureValidEpicParent(projectId, parentId, childId) {
|
|
224
|
-
if (!parentId) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (childId && parentId === childId) {
|
|
228
|
-
throw new error_types_1.ValidationError('An epic cannot be its own parent.', {
|
|
229
|
-
epicId: childId,
|
|
230
|
-
parentId,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
const parent = await this.getEpic(parentId);
|
|
234
|
-
if (parent.projectId !== projectId) {
|
|
235
|
-
throw new error_types_1.ValidationError('Parent epic must belong to the same project.', {
|
|
236
|
-
projectId,
|
|
237
|
-
parentProjectId: parent.projectId,
|
|
238
|
-
parentId,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
if (parent.parentId) {
|
|
242
|
-
throw new error_types_1.ValidationError('Cannot assign a sub-epic as a parent (one-level hierarchy).', {
|
|
243
|
-
parentId,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
if (childId) {
|
|
247
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
248
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
249
|
-
const descendants = await this.db
|
|
250
|
-
.select({ id: epics.id })
|
|
251
|
-
.from(epics)
|
|
252
|
-
.where(eq(epics.parentId, childId));
|
|
253
|
-
if (descendants.some((row) => row.id === parentId)) {
|
|
254
|
-
throw new error_types_1.ValidationError('Cannot assign a descendant as the parent epic.', {
|
|
255
|
-
parentId,
|
|
256
|
-
epicId: childId,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
async buildMcpHiddenExclusionPredicate(projectId, epicsTable) {
|
|
262
|
-
const { sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
263
|
-
return sql `${epicsTable.id} NOT IN (
|
|
264
|
-
WITH RECURSIVE excluded_tree AS (
|
|
265
|
-
SELECT e.id FROM epics e
|
|
266
|
-
JOIN statuses s ON e.status_id = s.id
|
|
267
|
-
WHERE s.mcp_hidden = 1 AND e.project_id = ${projectId}
|
|
268
|
-
UNION ALL
|
|
269
|
-
SELECT e.id FROM epics e
|
|
270
|
-
JOIN excluded_tree et ON e.parent_id = et.id
|
|
271
|
-
WHERE e.project_id = ${projectId}
|
|
272
|
-
)
|
|
273
|
-
SELECT id FROM excluded_tree
|
|
274
|
-
)`;
|
|
275
|
-
}
|
|
276
|
-
async ensureValidAgent(projectId, agentId) {
|
|
277
|
-
if (!agentId) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const agent = await this.getAgent(agentId);
|
|
281
|
-
if (agent.projectId !== projectId) {
|
|
282
|
-
throw new error_types_1.ValidationError('Agent must belong to the same project as the epic.', {
|
|
283
|
-
projectId,
|
|
284
|
-
agentProjectId: agent.projectId,
|
|
285
|
-
agentId,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
async listSeedableSourceNamesForNewProject() {
|
|
290
|
-
const { communitySkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
291
|
-
const communitySourceRows = await this.db
|
|
292
|
-
.select({ name: communitySkillSources.name })
|
|
293
|
-
.from(communitySkillSources);
|
|
294
|
-
const sourceNames = communitySourceRows
|
|
295
|
-
.map((row) => row.name.trim().toLowerCase())
|
|
296
|
-
.filter((name) => name.length > 0);
|
|
297
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
298
|
-
if (sqlite && typeof sqlite.prepare === 'function') {
|
|
299
|
-
try {
|
|
300
|
-
const localRows = sqlite.prepare('SELECT name FROM local_skill_sources').all();
|
|
301
|
-
for (const row of localRows) {
|
|
302
|
-
if (typeof row.name !== 'string') {
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
const normalized = row.name.trim().toLowerCase();
|
|
306
|
-
if (normalized.length > 0) {
|
|
307
|
-
sourceNames.push(normalized);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
313
|
-
if (!message.includes('no such table: local_skill_sources')) {
|
|
314
|
-
throw new error_types_1.StorageError('Failed to list local skill sources for project source seeding.', {
|
|
315
|
-
cause: message,
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return [...new Set(sourceNames)];
|
|
321
|
-
}
|
|
322
156
|
async createProject(data) {
|
|
323
|
-
|
|
324
|
-
const now = new Date().toISOString();
|
|
325
|
-
const project = {
|
|
326
|
-
id: randomUUID(),
|
|
327
|
-
...data,
|
|
328
|
-
isTemplate: data.isTemplate ?? false,
|
|
329
|
-
createdAt: now,
|
|
330
|
-
updatedAt: now,
|
|
331
|
-
};
|
|
332
|
-
const seedableSourceNames = await this.listSeedableSourceNamesForNewProject();
|
|
333
|
-
const { projects, statuses, sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
334
|
-
await this.db.transaction(async (tx) => {
|
|
335
|
-
await tx.insert(projects).values({
|
|
336
|
-
id: project.id,
|
|
337
|
-
name: project.name,
|
|
338
|
-
description: project.description,
|
|
339
|
-
rootPath: project.rootPath,
|
|
340
|
-
isTemplate: project.isTemplate,
|
|
341
|
-
createdAt: project.createdAt,
|
|
342
|
-
updatedAt: project.updatedAt,
|
|
343
|
-
});
|
|
344
|
-
const defaultStatuses = [
|
|
345
|
-
{ label: 'Proposed', color: '#6c757d', position: 0 },
|
|
346
|
-
{ label: 'In Progress', color: '#007bff', position: 1 },
|
|
347
|
-
{ label: 'Review', color: '#ffc107', position: 2 },
|
|
348
|
-
{ label: 'Done', color: '#28a745', position: 3 },
|
|
349
|
-
{ label: 'Blocked', color: '#dc3545', position: 4 },
|
|
350
|
-
];
|
|
351
|
-
for (const status of defaultStatuses) {
|
|
352
|
-
await tx.insert(statuses).values({
|
|
353
|
-
id: randomUUID(),
|
|
354
|
-
projectId: project.id,
|
|
355
|
-
label: status.label,
|
|
356
|
-
color: status.color,
|
|
357
|
-
position: status.position,
|
|
358
|
-
createdAt: now,
|
|
359
|
-
updatedAt: now,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
if (seedableSourceNames.length > 0) {
|
|
363
|
-
await tx.insert(sourceProjectEnabled).values(seedableSourceNames.map((sourceName) => ({
|
|
364
|
-
id: randomUUID(),
|
|
365
|
-
projectId: project.id,
|
|
366
|
-
sourceName,
|
|
367
|
-
enabled: true,
|
|
368
|
-
createdAt: now,
|
|
369
|
-
})));
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
logger.info({ projectId: project.id }, 'Created project with default statuses (transactional)');
|
|
373
|
-
return project;
|
|
157
|
+
return this.projectDelegate.createProject(data);
|
|
374
158
|
}
|
|
375
159
|
async createProjectWithTemplate(data, template, options) {
|
|
376
|
-
|
|
377
|
-
const now = new Date().toISOString();
|
|
378
|
-
const providedProjectId = options?.projectId?.trim();
|
|
379
|
-
const project = {
|
|
380
|
-
id: providedProjectId || randomUUID(),
|
|
381
|
-
...data,
|
|
382
|
-
isTemplate: data.isTemplate ?? false,
|
|
383
|
-
createdAt: now,
|
|
384
|
-
updatedAt: now,
|
|
385
|
-
};
|
|
386
|
-
const seedableSourceNames = await this.listSeedableSourceNamesForNewProject();
|
|
387
|
-
const { projects, statuses, prompts, agentProfiles, agents, tags, promptTags, profileProviderConfigs, providers, sourceProjectEnabled, } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
388
|
-
const statusIdMap = {};
|
|
389
|
-
const promptIdMap = {};
|
|
390
|
-
const profileIdMap = {};
|
|
391
|
-
const configIdMap = {};
|
|
392
|
-
const agentIdMap = {};
|
|
393
|
-
const createdPrompts = [];
|
|
394
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
395
|
-
if (!sqlite || typeof sqlite.exec !== 'function') {
|
|
396
|
-
throw new error_types_1.StorageError('Unable to access underlying SQLite client for transaction control');
|
|
397
|
-
}
|
|
398
|
-
sqlite.exec('BEGIN IMMEDIATE TRANSACTION');
|
|
399
|
-
try {
|
|
400
|
-
await this.db.insert(projects).values({
|
|
401
|
-
id: project.id,
|
|
402
|
-
name: project.name,
|
|
403
|
-
description: project.description,
|
|
404
|
-
rootPath: project.rootPath,
|
|
405
|
-
isTemplate: project.isTemplate,
|
|
406
|
-
createdAt: project.createdAt,
|
|
407
|
-
updatedAt: project.updatedAt,
|
|
408
|
-
});
|
|
409
|
-
for (const s of template.statuses.sort((a, b) => a.position - b.position)) {
|
|
410
|
-
const statusId = randomUUID();
|
|
411
|
-
await this.db.insert(statuses).values({
|
|
412
|
-
id: statusId,
|
|
413
|
-
projectId: project.id,
|
|
414
|
-
label: s.label,
|
|
415
|
-
color: s.color,
|
|
416
|
-
position: s.position,
|
|
417
|
-
mcpHidden: s.mcpHidden ?? false,
|
|
418
|
-
createdAt: now,
|
|
419
|
-
updatedAt: now,
|
|
420
|
-
});
|
|
421
|
-
if (s.id)
|
|
422
|
-
statusIdMap[s.id] = statusId;
|
|
423
|
-
}
|
|
424
|
-
if (seedableSourceNames.length > 0) {
|
|
425
|
-
await this.db.insert(sourceProjectEnabled).values(seedableSourceNames.map((sourceName) => ({
|
|
426
|
-
id: randomUUID(),
|
|
427
|
-
projectId: project.id,
|
|
428
|
-
sourceName,
|
|
429
|
-
enabled: true,
|
|
430
|
-
createdAt: now,
|
|
431
|
-
})));
|
|
432
|
-
}
|
|
433
|
-
const { eq, and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
434
|
-
for (const p of template.prompts) {
|
|
435
|
-
const promptId = randomUUID();
|
|
436
|
-
await this.db.insert(prompts).values({
|
|
437
|
-
id: promptId,
|
|
438
|
-
projectId: project.id,
|
|
439
|
-
title: p.title,
|
|
440
|
-
content: p.content ?? '',
|
|
441
|
-
version: 1,
|
|
442
|
-
createdAt: now,
|
|
443
|
-
updatedAt: now,
|
|
444
|
-
});
|
|
445
|
-
if (p.id)
|
|
446
|
-
promptIdMap[p.id] = promptId;
|
|
447
|
-
createdPrompts.push({ id: promptId, title: p.title });
|
|
448
|
-
if (p.tags?.length) {
|
|
449
|
-
for (const tagName of p.tags) {
|
|
450
|
-
let tag = await this.db
|
|
451
|
-
.select()
|
|
452
|
-
.from(tags)
|
|
453
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, project.id), isNull(tags.projectId))))
|
|
454
|
-
.limit(1);
|
|
455
|
-
if (!tag[0]) {
|
|
456
|
-
const tagId = randomUUID();
|
|
457
|
-
await this.db.insert(tags).values({
|
|
458
|
-
id: tagId,
|
|
459
|
-
projectId: project.id,
|
|
460
|
-
name: tagName,
|
|
461
|
-
createdAt: now,
|
|
462
|
-
updatedAt: now,
|
|
463
|
-
});
|
|
464
|
-
tag = [
|
|
465
|
-
{ id: tagId, projectId: project.id, name: tagName, createdAt: now, updatedAt: now },
|
|
466
|
-
];
|
|
467
|
-
}
|
|
468
|
-
await this.db.insert(promptTags).values({
|
|
469
|
-
promptId,
|
|
470
|
-
tagId: tag[0].id,
|
|
471
|
-
createdAt: now,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
for (const prof of template.profiles) {
|
|
477
|
-
const profileId = randomUUID();
|
|
478
|
-
await this.db.insert(agentProfiles).values({
|
|
479
|
-
id: profileId,
|
|
480
|
-
projectId: project.id,
|
|
481
|
-
name: prof.name,
|
|
482
|
-
familySlug: prof.familySlug ?? null,
|
|
483
|
-
systemPrompt: null,
|
|
484
|
-
instructions: prof.instructions,
|
|
485
|
-
temperature: prof.temperature != null ? Math.round(prof.temperature * 100) : null,
|
|
486
|
-
maxTokens: prof.maxTokens,
|
|
487
|
-
createdAt: now,
|
|
488
|
-
updatedAt: now,
|
|
489
|
-
});
|
|
490
|
-
if (prof.id)
|
|
491
|
-
profileIdMap[prof.id] = profileId;
|
|
492
|
-
if (prof.providerConfigs && prof.providerConfigs.length > 0) {
|
|
493
|
-
const sortedConfigs = [...prof.providerConfigs].sort((a, b) => {
|
|
494
|
-
const posA = a.position ?? 0;
|
|
495
|
-
const posB = b.position ?? 0;
|
|
496
|
-
return posA - posB;
|
|
497
|
-
});
|
|
498
|
-
for (const config of sortedConfigs) {
|
|
499
|
-
const provider = await this.db
|
|
500
|
-
.select()
|
|
501
|
-
.from(providers)
|
|
502
|
-
.where(eq(providers.name, config.providerName))
|
|
503
|
-
.limit(1);
|
|
504
|
-
if (!provider[0]) {
|
|
505
|
-
throw new error_types_1.ValidationError(`Provider not found: ${config.providerName}`);
|
|
506
|
-
}
|
|
507
|
-
const configId = randomUUID();
|
|
508
|
-
await this.db.insert(profileProviderConfigs).values({
|
|
509
|
-
id: configId,
|
|
510
|
-
profileId: profileId,
|
|
511
|
-
providerId: provider[0].id,
|
|
512
|
-
name: config.name,
|
|
513
|
-
options: config.options ?? null,
|
|
514
|
-
env: config.env ? JSON.stringify(config.env) : null,
|
|
515
|
-
position: config.position ?? sortedConfigs.indexOf(config),
|
|
516
|
-
createdAt: now,
|
|
517
|
-
updatedAt: now,
|
|
518
|
-
});
|
|
519
|
-
if (!configIdMap[profileId]) {
|
|
520
|
-
configIdMap[profileId] = configId;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
const configId = randomUUID();
|
|
526
|
-
await this.db.insert(profileProviderConfigs).values({
|
|
527
|
-
id: configId,
|
|
528
|
-
profileId: profileId,
|
|
529
|
-
providerId: prof.providerId,
|
|
530
|
-
name: prof.name,
|
|
531
|
-
options: prof.options ?? null,
|
|
532
|
-
env: null,
|
|
533
|
-
position: 0,
|
|
534
|
-
createdAt: now,
|
|
535
|
-
updatedAt: now,
|
|
536
|
-
});
|
|
537
|
-
configIdMap[profileId] = configId;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
for (const a of template.agents) {
|
|
541
|
-
const oldProfileId = a.profileId ?? '';
|
|
542
|
-
const newProfileId = oldProfileId && profileIdMap[oldProfileId] ? profileIdMap[oldProfileId] : undefined;
|
|
543
|
-
if (!newProfileId) {
|
|
544
|
-
throw new error_types_1.ValidationError(`Profile mapping missing for agent ${a.name}`, {
|
|
545
|
-
profileId: oldProfileId || null,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
const configId = configIdMap[newProfileId];
|
|
549
|
-
if (!configId) {
|
|
550
|
-
throw new error_types_1.ValidationError(`Config mapping missing for agent ${a.name}`, {
|
|
551
|
-
profileId: newProfileId,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
const agentId = randomUUID();
|
|
555
|
-
await this.db.insert(agents).values({
|
|
556
|
-
id: agentId,
|
|
557
|
-
projectId: project.id,
|
|
558
|
-
name: a.name,
|
|
559
|
-
profileId: newProfileId,
|
|
560
|
-
providerConfigId: configId,
|
|
561
|
-
description: a.description ?? null,
|
|
562
|
-
createdAt: now,
|
|
563
|
-
updatedAt: now,
|
|
564
|
-
});
|
|
565
|
-
if (a.id)
|
|
566
|
-
agentIdMap[a.id] = agentId;
|
|
567
|
-
}
|
|
568
|
-
sqlite.exec('COMMIT');
|
|
569
|
-
logger.info({ projectId: project.id, counts: template }, 'Created project with template (transactional)');
|
|
570
|
-
}
|
|
571
|
-
catch (error) {
|
|
572
|
-
try {
|
|
573
|
-
sqlite.exec('ROLLBACK');
|
|
574
|
-
logger.info({ projectId: project.id }, 'Transaction rolled back successfully');
|
|
575
|
-
}
|
|
576
|
-
catch (rollbackError) {
|
|
577
|
-
logger.error({ rollbackError }, 'Failed to rollback transaction');
|
|
578
|
-
}
|
|
579
|
-
logger.error({ error, projectId: project.id }, 'Transaction failed');
|
|
580
|
-
const errorMessage = error instanceof Error ? error.message : '';
|
|
581
|
-
if (providedProjectId &&
|
|
582
|
-
this.isSqliteUniqueConstraint(error) &&
|
|
583
|
-
errorMessage.includes('projects.id')) {
|
|
584
|
-
throw new error_types_1.ConflictError(`Project ID "${providedProjectId}" already exists.`, {
|
|
585
|
-
field: 'projectId',
|
|
586
|
-
projectId: providedProjectId,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
throw error;
|
|
590
|
-
}
|
|
591
|
-
return {
|
|
592
|
-
project,
|
|
593
|
-
imported: {
|
|
594
|
-
prompts: template.prompts.length,
|
|
595
|
-
profiles: template.profiles.length,
|
|
596
|
-
agents: template.agents.length,
|
|
597
|
-
statuses: template.statuses.length,
|
|
598
|
-
},
|
|
599
|
-
mappings: {
|
|
600
|
-
promptIdMap,
|
|
601
|
-
profileIdMap,
|
|
602
|
-
agentIdMap,
|
|
603
|
-
statusIdMap,
|
|
604
|
-
},
|
|
605
|
-
initialPromptSet: false,
|
|
606
|
-
};
|
|
160
|
+
return this.projectDelegate.createProjectWithTemplate(data, template, options);
|
|
607
161
|
}
|
|
608
162
|
async getProject(id) {
|
|
609
|
-
|
|
610
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
611
|
-
const result = await this.db.select().from(projects).where(eq(projects.id, id)).limit(1);
|
|
612
|
-
if (!result[0]) {
|
|
613
|
-
throw new error_types_1.NotFoundError('Project', id);
|
|
614
|
-
}
|
|
615
|
-
const row = result[0];
|
|
616
|
-
return {
|
|
617
|
-
id: row.id,
|
|
618
|
-
name: row.name,
|
|
619
|
-
description: row.description ?? null,
|
|
620
|
-
rootPath: row.rootPath,
|
|
621
|
-
isTemplate: Boolean(row.isTemplate ?? false),
|
|
622
|
-
createdAt: row.createdAt,
|
|
623
|
-
updatedAt: row.updatedAt,
|
|
624
|
-
};
|
|
163
|
+
return this.projectDelegate.getProject(id);
|
|
625
164
|
}
|
|
626
165
|
async findProjectByPath(path) {
|
|
627
|
-
|
|
628
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
629
|
-
const result = await this.db
|
|
630
|
-
.select()
|
|
631
|
-
.from(projects)
|
|
632
|
-
.where(eq(projects.rootPath, path))
|
|
633
|
-
.limit(1);
|
|
634
|
-
if (!result[0])
|
|
635
|
-
return null;
|
|
636
|
-
const row = result[0];
|
|
637
|
-
return {
|
|
638
|
-
id: row.id,
|
|
639
|
-
name: row.name,
|
|
640
|
-
description: row.description ?? null,
|
|
641
|
-
rootPath: row.rootPath,
|
|
642
|
-
isTemplate: Boolean(row.isTemplate ?? false),
|
|
643
|
-
createdAt: row.createdAt,
|
|
644
|
-
updatedAt: row.updatedAt,
|
|
645
|
-
};
|
|
166
|
+
return this.projectDelegate.findProjectByPath(path);
|
|
646
167
|
}
|
|
647
168
|
async listProjects(options = {}) {
|
|
648
|
-
|
|
649
|
-
const { sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
650
|
-
const limit = options.limit || 100;
|
|
651
|
-
const offset = options.offset || 0;
|
|
652
|
-
const items = await this.db.select().from(projects).limit(limit).offset(offset);
|
|
653
|
-
const countResult = await this.db.select({ count: sql `count(*)` }).from(projects);
|
|
654
|
-
const total = Number(countResult[0]?.count ?? 0);
|
|
655
|
-
const mapped = items.map((row) => ({
|
|
656
|
-
id: row.id,
|
|
657
|
-
name: row.name,
|
|
658
|
-
description: row.description ?? null,
|
|
659
|
-
rootPath: row.rootPath,
|
|
660
|
-
isTemplate: Boolean(row.isTemplate ?? false),
|
|
661
|
-
createdAt: row.createdAt,
|
|
662
|
-
updatedAt: row.updatedAt,
|
|
663
|
-
}));
|
|
664
|
-
return {
|
|
665
|
-
items: mapped,
|
|
666
|
-
total,
|
|
667
|
-
limit,
|
|
668
|
-
offset,
|
|
669
|
-
};
|
|
169
|
+
return this.projectDelegate.listProjects(options);
|
|
670
170
|
}
|
|
671
171
|
async updateProject(id, data) {
|
|
672
|
-
|
|
673
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
674
|
-
const now = new Date().toISOString();
|
|
675
|
-
await this.db
|
|
676
|
-
.update(projects)
|
|
677
|
-
.set({ ...data, updatedAt: now })
|
|
678
|
-
.where(eq(projects.id, id));
|
|
679
|
-
return this.getProject(id);
|
|
172
|
+
return this.projectDelegate.updateProject(id, data);
|
|
680
173
|
}
|
|
681
174
|
async deleteProject(id) {
|
|
682
|
-
|
|
683
|
-
const { eq, inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
684
|
-
const projectEpics = await this.db
|
|
685
|
-
.select({ id: epics.id })
|
|
686
|
-
.from(epics)
|
|
687
|
-
.where(eq(epics.projectId, id));
|
|
688
|
-
const epicIds = projectEpics.map((e) => e.id);
|
|
689
|
-
const projectChatThreads = await this.db
|
|
690
|
-
.select({ id: chatThreads.id })
|
|
691
|
-
.from(chatThreads)
|
|
692
|
-
.where(eq(chatThreads.projectId, id));
|
|
693
|
-
const threadIds = projectChatThreads.map((t) => t.id);
|
|
694
|
-
const projectMessages = threadIds.length > 0
|
|
695
|
-
? await this.db
|
|
696
|
-
.select({ id: chatMessages.id })
|
|
697
|
-
.from(chatMessages)
|
|
698
|
-
.where(inArray(chatMessages.threadId, threadIds))
|
|
699
|
-
: [];
|
|
700
|
-
const messageIds = projectMessages.map((m) => m.id);
|
|
701
|
-
const projectAgents = await this.db
|
|
702
|
-
.select({ id: agents.id })
|
|
703
|
-
.from(agents)
|
|
704
|
-
.where(eq(agents.projectId, id));
|
|
705
|
-
const agentIds = projectAgents.map((a) => a.id);
|
|
706
|
-
const projectDocs = await this.db
|
|
707
|
-
.select({ id: documents.id })
|
|
708
|
-
.from(documents)
|
|
709
|
-
.where(eq(documents.projectId, id));
|
|
710
|
-
const docIds = projectDocs.map((d) => d.id);
|
|
711
|
-
const projectPrompts = await this.db
|
|
712
|
-
.select({ id: prompts.id })
|
|
713
|
-
.from(prompts)
|
|
714
|
-
.where(eq(prompts.projectId, id));
|
|
715
|
-
const promptIds = projectPrompts.map((p) => p.id);
|
|
716
|
-
const projectProfiles = await this.db
|
|
717
|
-
.select({ id: agentProfiles.id })
|
|
718
|
-
.from(agentProfiles)
|
|
719
|
-
.where(eq(agentProfiles.projectId, id));
|
|
720
|
-
const profileIds = projectProfiles.map((p) => p.id);
|
|
721
|
-
const projectTags = await this.db
|
|
722
|
-
.select({ id: tags.id })
|
|
723
|
-
.from(tags)
|
|
724
|
-
.where(eq(tags.projectId, id));
|
|
725
|
-
const tagIds = projectTags.map((t) => t.id);
|
|
726
|
-
const projectSessions = agentIds.length > 0
|
|
727
|
-
? await this.db
|
|
728
|
-
.select({ id: sessions.id })
|
|
729
|
-
.from(sessions)
|
|
730
|
-
.where(inArray(sessions.agentId, agentIds))
|
|
731
|
-
: [];
|
|
732
|
-
const sessionIds = projectSessions.map((s) => s.id);
|
|
733
|
-
if (messageIds.length > 0) {
|
|
734
|
-
await this.db.delete(chatMessageReads).where(inArray(chatMessageReads.messageId, messageIds));
|
|
735
|
-
await this.db
|
|
736
|
-
.delete(chatMessageTargets)
|
|
737
|
-
.where(inArray(chatMessageTargets.messageId, messageIds));
|
|
738
|
-
await this.db
|
|
739
|
-
.delete(chatThreadSessionInvites)
|
|
740
|
-
.where(inArray(chatThreadSessionInvites.inviteMessageId, messageIds));
|
|
741
|
-
}
|
|
742
|
-
if (agentIds.length > 0) {
|
|
743
|
-
await this.db.delete(chatMessageReads).where(inArray(chatMessageReads.agentId, agentIds));
|
|
744
|
-
await this.db.delete(chatMessageTargets).where(inArray(chatMessageTargets.agentId, agentIds));
|
|
745
|
-
await this.db
|
|
746
|
-
.delete(chatThreadSessionInvites)
|
|
747
|
-
.where(inArray(chatThreadSessionInvites.agentId, agentIds));
|
|
748
|
-
await this.db.delete(chatActivities).where(inArray(chatActivities.agentId, agentIds));
|
|
749
|
-
await this.db.delete(chatMembers).where(inArray(chatMembers.agentId, agentIds));
|
|
750
|
-
}
|
|
751
|
-
if (messageIds.length > 0) {
|
|
752
|
-
await this.db.delete(chatMessages).where(inArray(chatMessages.threadId, threadIds));
|
|
753
|
-
}
|
|
754
|
-
if (threadIds.length > 0) {
|
|
755
|
-
await this.db.delete(chatThreads).where(inArray(chatThreads.id, threadIds));
|
|
756
|
-
}
|
|
757
|
-
if (sessionIds.length > 0) {
|
|
758
|
-
await this.db.delete(transcripts).where(inArray(transcripts.sessionId, sessionIds));
|
|
759
|
-
await this.db.delete(sessions).where(inArray(sessions.id, sessionIds));
|
|
760
|
-
}
|
|
761
|
-
if (epicIds.length > 0) {
|
|
762
|
-
await this.db.delete(epicComments).where(inArray(epicComments.epicId, epicIds));
|
|
763
|
-
const projectRecords = await this.db
|
|
764
|
-
.select({ id: records.id })
|
|
765
|
-
.from(records)
|
|
766
|
-
.where(inArray(records.epicId, epicIds));
|
|
767
|
-
const recordIds = projectRecords.map((r) => r.id);
|
|
768
|
-
if (recordIds.length > 0) {
|
|
769
|
-
await this.db.delete(recordTags).where(inArray(recordTags.recordId, recordIds));
|
|
770
|
-
await this.db.delete(records).where(inArray(records.id, recordIds));
|
|
771
|
-
}
|
|
772
|
-
await this.db.delete(epicTags).where(inArray(epicTags.epicId, epicIds));
|
|
773
|
-
}
|
|
774
|
-
if (epicIds.length > 0) {
|
|
775
|
-
await this.db.delete(epics).where(inArray(epics.id, epicIds));
|
|
776
|
-
}
|
|
777
|
-
if (docIds.length > 0) {
|
|
778
|
-
await this.db.delete(documentTags).where(inArray(documentTags.documentId, docIds));
|
|
779
|
-
await this.db.delete(documents).where(inArray(documents.id, docIds));
|
|
780
|
-
}
|
|
781
|
-
if (promptIds.length > 0) {
|
|
782
|
-
await this.db.delete(promptTags).where(inArray(promptTags.promptId, promptIds));
|
|
783
|
-
await this.db
|
|
784
|
-
.delete(agentProfilePrompts)
|
|
785
|
-
.where(inArray(agentProfilePrompts.promptId, promptIds));
|
|
786
|
-
await this.db.delete(prompts).where(inArray(prompts.id, promptIds));
|
|
787
|
-
}
|
|
788
|
-
if (agentIds.length > 0) {
|
|
789
|
-
await this.db.delete(agents).where(inArray(agents.id, agentIds));
|
|
790
|
-
}
|
|
791
|
-
if (profileIds.length > 0) {
|
|
792
|
-
await this.db
|
|
793
|
-
.delete(agentProfilePrompts)
|
|
794
|
-
.where(inArray(agentProfilePrompts.profileId, profileIds));
|
|
795
|
-
await this.db.delete(agentProfiles).where(inArray(agentProfiles.id, profileIds));
|
|
796
|
-
}
|
|
797
|
-
if (tagIds.length > 0) {
|
|
798
|
-
await this.db.delete(tags).where(inArray(tags.id, tagIds));
|
|
799
|
-
}
|
|
800
|
-
await this.db.delete(statuses).where(eq(statuses.projectId, id));
|
|
801
|
-
await this.db.delete(guests).where(eq(guests.projectId, id));
|
|
802
|
-
await this.db.delete(projects).where(eq(projects.id, id));
|
|
803
|
-
logger.info({ projectId: id }, 'Deleted project and all related records');
|
|
175
|
+
return this.projectDelegate.deleteProject(id);
|
|
804
176
|
}
|
|
805
177
|
async createStatus(data) {
|
|
806
|
-
|
|
807
|
-
const now = new Date().toISOString();
|
|
808
|
-
const { statuses } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
809
|
-
const status = {
|
|
810
|
-
id: randomUUID(),
|
|
811
|
-
...data,
|
|
812
|
-
mcpHidden: data.mcpHidden ?? false,
|
|
813
|
-
createdAt: now,
|
|
814
|
-
updatedAt: now,
|
|
815
|
-
};
|
|
816
|
-
await this.db.insert(statuses).values({
|
|
817
|
-
id: status.id,
|
|
818
|
-
projectId: status.projectId,
|
|
819
|
-
label: status.label,
|
|
820
|
-
color: status.color,
|
|
821
|
-
position: status.position,
|
|
822
|
-
mcpHidden: status.mcpHidden,
|
|
823
|
-
createdAt: status.createdAt,
|
|
824
|
-
updatedAt: status.updatedAt,
|
|
825
|
-
});
|
|
826
|
-
return status;
|
|
178
|
+
return this.statusDelegate.createStatus(data);
|
|
827
179
|
}
|
|
828
180
|
async getStatus(id) {
|
|
829
|
-
|
|
830
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
831
|
-
const result = await this.db.select().from(statuses).where(eq(statuses.id, id)).limit(1);
|
|
832
|
-
if (!result[0]) {
|
|
833
|
-
throw new error_types_1.NotFoundError('Status', id);
|
|
834
|
-
}
|
|
835
|
-
return result[0];
|
|
181
|
+
return this.statusDelegate.getStatus(id);
|
|
836
182
|
}
|
|
837
183
|
async listStatuses(projectId, options = {}) {
|
|
838
|
-
|
|
839
|
-
const { eq, asc, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
840
|
-
const limit = options.limit || 100;
|
|
841
|
-
const offset = options.offset || 0;
|
|
842
|
-
const items = await this.db
|
|
843
|
-
.select()
|
|
844
|
-
.from(statuses)
|
|
845
|
-
.where(eq(statuses.projectId, projectId))
|
|
846
|
-
.orderBy(asc(statuses.position))
|
|
847
|
-
.limit(limit)
|
|
848
|
-
.offset(offset);
|
|
849
|
-
const totalResult = await this.db
|
|
850
|
-
.select({ count: sql `count(*)` })
|
|
851
|
-
.from(statuses)
|
|
852
|
-
.where(eq(statuses.projectId, projectId));
|
|
853
|
-
const total = Number(totalResult[0]?.count ?? 0);
|
|
854
|
-
return {
|
|
855
|
-
items: items,
|
|
856
|
-
total,
|
|
857
|
-
limit,
|
|
858
|
-
offset,
|
|
859
|
-
};
|
|
184
|
+
return this.statusDelegate.listStatuses(projectId, options);
|
|
860
185
|
}
|
|
861
186
|
async findStatusByName(projectId, name) {
|
|
862
|
-
|
|
863
|
-
if (!normalized) {
|
|
864
|
-
return null;
|
|
865
|
-
}
|
|
866
|
-
const { statuses } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
867
|
-
const { and, eq, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
868
|
-
const result = await this.db
|
|
869
|
-
.select()
|
|
870
|
-
.from(statuses)
|
|
871
|
-
.where(and(eq(statuses.projectId, projectId), sql `lower(${statuses.label}) = ${normalized}`))
|
|
872
|
-
.limit(1);
|
|
873
|
-
return result[0] ? result[0] : null;
|
|
187
|
+
return this.statusDelegate.findStatusByName(projectId, name);
|
|
874
188
|
}
|
|
875
189
|
async updateStatus(id, data) {
|
|
876
|
-
|
|
877
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
878
|
-
const now = new Date().toISOString();
|
|
879
|
-
await this.db
|
|
880
|
-
.update(statuses)
|
|
881
|
-
.set({ ...data, updatedAt: now })
|
|
882
|
-
.where(eq(statuses.id, id));
|
|
883
|
-
return this.getStatus(id);
|
|
190
|
+
return this.statusDelegate.updateStatus(id, data);
|
|
884
191
|
}
|
|
885
192
|
async deleteStatus(id) {
|
|
886
|
-
|
|
887
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
888
|
-
await this.db.delete(statuses).where(eq(statuses.id, id));
|
|
193
|
+
return this.statusDelegate.deleteStatus(id);
|
|
889
194
|
}
|
|
890
195
|
async createEpic(data) {
|
|
891
|
-
|
|
892
|
-
const now = new Date().toISOString();
|
|
893
|
-
const { epics, epicTags, tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
894
|
-
const epicId = randomUUID();
|
|
895
|
-
await this.ensureValidEpicParent(data.projectId, data.parentId ?? null, epicId);
|
|
896
|
-
await this.ensureValidAgent(data.projectId, data.agentId ?? null);
|
|
897
|
-
const epic = {
|
|
898
|
-
id: epicId,
|
|
899
|
-
projectId: data.projectId,
|
|
900
|
-
title: data.title,
|
|
901
|
-
description: data.description ?? null,
|
|
902
|
-
statusId: data.statusId,
|
|
903
|
-
parentId: data.parentId ?? null,
|
|
904
|
-
agentId: data.agentId ?? null,
|
|
905
|
-
version: 1,
|
|
906
|
-
data: data.data ?? null,
|
|
907
|
-
skillsRequired: data.skillsRequired ?? null,
|
|
908
|
-
tags: data.tags ?? [],
|
|
909
|
-
createdAt: now,
|
|
910
|
-
updatedAt: now,
|
|
911
|
-
};
|
|
912
|
-
await this.db.insert(epics).values({
|
|
913
|
-
id: epic.id,
|
|
914
|
-
projectId: epic.projectId,
|
|
915
|
-
title: epic.title,
|
|
916
|
-
description: epic.description,
|
|
917
|
-
statusId: epic.statusId,
|
|
918
|
-
parentId: epic.parentId,
|
|
919
|
-
agentId: epic.agentId,
|
|
920
|
-
version: epic.version,
|
|
921
|
-
data: epic.data ? JSON.stringify(epic.data) : null,
|
|
922
|
-
skillsRequired: this.serializeSkillsRequired(epic.skillsRequired),
|
|
923
|
-
createdAt: epic.createdAt,
|
|
924
|
-
updatedAt: epic.updatedAt,
|
|
925
|
-
});
|
|
926
|
-
if (epic.tags.length) {
|
|
927
|
-
for (const tagName of epic.tags) {
|
|
928
|
-
const { eq, and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
929
|
-
let tag = await this.db
|
|
930
|
-
.select()
|
|
931
|
-
.from(tags)
|
|
932
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, data.projectId), isNull(tags.projectId))))
|
|
933
|
-
.limit(1);
|
|
934
|
-
if (!tag[0]) {
|
|
935
|
-
const newTag = await this.createTag({ projectId: data.projectId, name: tagName });
|
|
936
|
-
tag = [newTag];
|
|
937
|
-
}
|
|
938
|
-
await this.db.insert(epicTags).values({
|
|
939
|
-
epicId: epic.id,
|
|
940
|
-
tagId: tag[0].id,
|
|
941
|
-
createdAt: now,
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
return epic;
|
|
196
|
+
return this.epicDelegate.createEpic(data);
|
|
946
197
|
}
|
|
947
198
|
async getEpic(id) {
|
|
948
|
-
|
|
949
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
950
|
-
const result = await this.db.select().from(epics).where(eq(epics.id, id)).limit(1);
|
|
951
|
-
if (!result[0]) {
|
|
952
|
-
throw new error_types_1.NotFoundError('Epic', id);
|
|
953
|
-
}
|
|
954
|
-
const epicTagsResult = await this.db
|
|
955
|
-
.select({ tag: tags })
|
|
956
|
-
.from(epicTags)
|
|
957
|
-
.innerJoin(tags, eq(epicTags.tagId, tags.id))
|
|
958
|
-
.where(eq(epicTags.epicId, id));
|
|
959
|
-
return {
|
|
960
|
-
...result[0],
|
|
961
|
-
data: result[0].data,
|
|
962
|
-
skillsRequired: this.parseSkillsRequired(result[0].skillsRequired),
|
|
963
|
-
tags: epicTagsResult.map((et) => et.tag.name),
|
|
964
|
-
};
|
|
199
|
+
return this.epicDelegate.getEpic(id);
|
|
965
200
|
}
|
|
966
201
|
async listEpics(projectId, options = {}) {
|
|
967
|
-
|
|
968
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
969
|
-
const limit = options.limit || 100;
|
|
970
|
-
const offset = options.offset || 0;
|
|
971
|
-
const items = await this.db
|
|
972
|
-
.select()
|
|
973
|
-
.from(epics)
|
|
974
|
-
.where(eq(epics.projectId, projectId))
|
|
975
|
-
.limit(limit)
|
|
976
|
-
.offset(offset);
|
|
977
|
-
const epicIds = items.map((item) => item.id);
|
|
978
|
-
const tagsMap = await this.batchFetchTags(epicIds);
|
|
979
|
-
const itemsWithTags = items.map((item) => ({
|
|
980
|
-
...item,
|
|
981
|
-
data: item.data,
|
|
982
|
-
skillsRequired: this.parseSkillsRequired(item.skillsRequired),
|
|
983
|
-
tags: tagsMap.get(item.id) ?? [],
|
|
984
|
-
}));
|
|
985
|
-
return {
|
|
986
|
-
items: itemsWithTags,
|
|
987
|
-
total: items.length,
|
|
988
|
-
limit,
|
|
989
|
-
offset,
|
|
990
|
-
};
|
|
202
|
+
return this.epicDelegate.listEpics(projectId, options);
|
|
991
203
|
}
|
|
992
204
|
async listEpicsByStatus(statusId, options = {}) {
|
|
993
|
-
|
|
994
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
995
|
-
const limit = options.limit || 100;
|
|
996
|
-
const offset = options.offset || 0;
|
|
997
|
-
const items = await this.db
|
|
998
|
-
.select()
|
|
999
|
-
.from(epics)
|
|
1000
|
-
.where(eq(epics.statusId, statusId))
|
|
1001
|
-
.limit(limit)
|
|
1002
|
-
.offset(offset);
|
|
1003
|
-
const epicIds = items.map((item) => item.id);
|
|
1004
|
-
const tagsMap = await this.batchFetchTags(epicIds);
|
|
1005
|
-
const itemsWithTags = items.map((item) => ({
|
|
1006
|
-
...item,
|
|
1007
|
-
data: item.data,
|
|
1008
|
-
skillsRequired: this.parseSkillsRequired(item.skillsRequired),
|
|
1009
|
-
tags: tagsMap.get(item.id) ?? [],
|
|
1010
|
-
}));
|
|
1011
|
-
return {
|
|
1012
|
-
items: itemsWithTags,
|
|
1013
|
-
total: items.length,
|
|
1014
|
-
limit,
|
|
1015
|
-
offset,
|
|
1016
|
-
};
|
|
205
|
+
return this.epicDelegate.listEpicsByStatus(statusId, options);
|
|
1017
206
|
}
|
|
1018
207
|
async listProjectEpics(projectId, options = {}) {
|
|
1019
|
-
|
|
1020
|
-
const { eq, and, sql, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1021
|
-
const limit = options.limit ?? 100;
|
|
1022
|
-
const offset = options.offset ?? 0;
|
|
1023
|
-
const conditions = [eq(epics.projectId, projectId)];
|
|
1024
|
-
if (options.q) {
|
|
1025
|
-
const search = options.q.trim().toLowerCase();
|
|
1026
|
-
if (search.length) {
|
|
1027
|
-
const pattern = `%${search}%`;
|
|
1028
|
-
const isUuidPrefix = search.length >= 8 && /^[a-f0-9-]+$/.test(search);
|
|
1029
|
-
if (isUuidPrefix) {
|
|
1030
|
-
const idPrefixPattern = `${search}%`;
|
|
1031
|
-
conditions.push(sql `(lower(${epics.title}) LIKE ${pattern} OR lower(ifnull(${epics.description}, '')) LIKE ${pattern} OR ${epics.id} LIKE ${idPrefixPattern})`);
|
|
1032
|
-
}
|
|
1033
|
-
else {
|
|
1034
|
-
conditions.push(sql `(lower(${epics.title}) LIKE ${pattern} OR lower(ifnull(${epics.description}, '')) LIKE ${pattern})`);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (options.statusId) {
|
|
1039
|
-
conditions.push(eq(epics.statusId, options.statusId));
|
|
1040
|
-
}
|
|
1041
|
-
const listType = (options.type ?? 'active').toLowerCase();
|
|
1042
|
-
let archivedFilter = null;
|
|
1043
|
-
if (listType === 'active') {
|
|
1044
|
-
archivedFilter = sql `lower(${statuses.label}) NOT LIKE '%archiv%'`;
|
|
1045
|
-
}
|
|
1046
|
-
else if (listType === 'archived') {
|
|
1047
|
-
archivedFilter = sql `lower(${statuses.label}) LIKE '%archiv%'`;
|
|
1048
|
-
}
|
|
1049
|
-
if (archivedFilter) {
|
|
1050
|
-
conditions.push(archivedFilter);
|
|
1051
|
-
}
|
|
1052
|
-
if (options.excludeMcpHidden) {
|
|
1053
|
-
conditions.push(await this.buildMcpHiddenExclusionPredicate(projectId, epics));
|
|
1054
|
-
}
|
|
1055
|
-
if (options.parentOnly) {
|
|
1056
|
-
conditions.push(sql `${epics.parentId} IS NULL`);
|
|
1057
|
-
}
|
|
1058
|
-
const whereClause = and(...conditions);
|
|
1059
|
-
const totalResult = await this.db
|
|
1060
|
-
.select({ count: sql `count(*)` })
|
|
1061
|
-
.from(epics)
|
|
1062
|
-
.innerJoin(statuses, eq(statuses.id, epics.statusId))
|
|
1063
|
-
.where(whereClause);
|
|
1064
|
-
const total = Number(totalResult[0]?.count ?? 0);
|
|
1065
|
-
const rows = await this.db
|
|
1066
|
-
.select({ epic: epics })
|
|
1067
|
-
.from(epics)
|
|
1068
|
-
.innerJoin(statuses, eq(statuses.id, epics.statusId))
|
|
1069
|
-
.where(whereClause)
|
|
1070
|
-
.orderBy(desc(epics.updatedAt))
|
|
1071
|
-
.limit(limit)
|
|
1072
|
-
.offset(offset);
|
|
1073
|
-
const epicIds = rows.map((row) => row.epic.id);
|
|
1074
|
-
const tagsMap = await this.batchFetchTags(epicIds);
|
|
1075
|
-
const items = rows.map((row) => ({
|
|
1076
|
-
...row.epic,
|
|
1077
|
-
data: row.epic.data,
|
|
1078
|
-
skillsRequired: this.parseSkillsRequired(row.epic.skillsRequired),
|
|
1079
|
-
tags: tagsMap.get(row.epic.id) ?? [],
|
|
1080
|
-
}));
|
|
1081
|
-
return {
|
|
1082
|
-
items,
|
|
1083
|
-
total,
|
|
1084
|
-
limit,
|
|
1085
|
-
offset,
|
|
1086
|
-
};
|
|
208
|
+
return this.epicDelegate.listProjectEpics(projectId, options);
|
|
1087
209
|
}
|
|
1088
210
|
async listAssignedEpics(projectId, options) {
|
|
1089
|
-
|
|
1090
|
-
throw new error_types_1.ValidationError('agentName is required to list assigned epics.', {
|
|
1091
|
-
projectId,
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1095
|
-
const { and, eq, sql, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1096
|
-
const limit = options.limit ?? 100;
|
|
1097
|
-
const offset = options.offset ?? 0;
|
|
1098
|
-
const agent = await this.getAgentByName(projectId, options.agentName);
|
|
1099
|
-
const conditions = [
|
|
1100
|
-
eq(epics.projectId, projectId),
|
|
1101
|
-
eq(epics.agentId, agent.id),
|
|
1102
|
-
];
|
|
1103
|
-
if (options.excludeMcpHidden) {
|
|
1104
|
-
conditions.push(await this.buildMcpHiddenExclusionPredicate(projectId, epics));
|
|
1105
|
-
}
|
|
1106
|
-
const whereClause = and(...conditions);
|
|
1107
|
-
const totalResult = await this.db
|
|
1108
|
-
.select({ count: sql `count(*)` })
|
|
1109
|
-
.from(epics)
|
|
1110
|
-
.where(whereClause);
|
|
1111
|
-
const total = Number(totalResult[0]?.count ?? 0);
|
|
1112
|
-
const rows = await this.db
|
|
1113
|
-
.select()
|
|
1114
|
-
.from(epics)
|
|
1115
|
-
.where(whereClause)
|
|
1116
|
-
.orderBy(desc(epics.updatedAt))
|
|
1117
|
-
.limit(limit)
|
|
1118
|
-
.offset(offset);
|
|
1119
|
-
const epicIds = rows.map((row) => row.id);
|
|
1120
|
-
const tagsMap = await this.batchFetchTags(epicIds);
|
|
1121
|
-
const items = rows.map((row) => ({
|
|
1122
|
-
...row,
|
|
1123
|
-
data: row.data,
|
|
1124
|
-
skillsRequired: this.parseSkillsRequired(row.skillsRequired),
|
|
1125
|
-
tags: tagsMap.get(row.id) ?? [],
|
|
1126
|
-
}));
|
|
1127
|
-
return {
|
|
1128
|
-
items,
|
|
1129
|
-
total,
|
|
1130
|
-
limit,
|
|
1131
|
-
offset,
|
|
1132
|
-
};
|
|
211
|
+
return this.epicDelegate.listAssignedEpics(projectId, options);
|
|
1133
212
|
}
|
|
1134
213
|
async createEpicForProject(projectId, input) {
|
|
1135
|
-
|
|
1136
|
-
const { eq, asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1137
|
-
let statusId = input.statusId ?? null;
|
|
1138
|
-
if (statusId) {
|
|
1139
|
-
const status = await this.getStatus(statusId);
|
|
1140
|
-
if (status.projectId !== projectId) {
|
|
1141
|
-
throw new error_types_1.ValidationError('Status must belong to the target project.', {
|
|
1142
|
-
statusId,
|
|
1143
|
-
projectId,
|
|
1144
|
-
statusProjectId: status.projectId,
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
else {
|
|
1149
|
-
const defaultStatusResult = await this.db
|
|
1150
|
-
.select({ id: statuses.id })
|
|
1151
|
-
.from(statuses)
|
|
1152
|
-
.where(eq(statuses.projectId, projectId))
|
|
1153
|
-
.orderBy(asc(statuses.position))
|
|
1154
|
-
.limit(1);
|
|
1155
|
-
const defaultStatus = defaultStatusResult[0];
|
|
1156
|
-
if (!defaultStatus) {
|
|
1157
|
-
throw new error_types_1.ValidationError('Project has no statuses configured.', { projectId });
|
|
1158
|
-
}
|
|
1159
|
-
statusId = defaultStatus.id;
|
|
1160
|
-
}
|
|
1161
|
-
let agentId = input.agentId ?? null;
|
|
1162
|
-
if (!agentId && input.agentName?.trim()) {
|
|
1163
|
-
const agent = await this.getAgentByName(projectId, input.agentName);
|
|
1164
|
-
agentId = agent.id;
|
|
1165
|
-
}
|
|
1166
|
-
await this.ensureValidAgent(projectId, agentId);
|
|
1167
|
-
await this.ensureValidEpicParent(projectId, input.parentId ?? null);
|
|
1168
|
-
return this.createEpic({
|
|
1169
|
-
projectId,
|
|
1170
|
-
title: input.title,
|
|
1171
|
-
description: input.description ?? null,
|
|
1172
|
-
statusId,
|
|
1173
|
-
parentId: input.parentId ?? null,
|
|
1174
|
-
agentId,
|
|
1175
|
-
skillsRequired: input.skillsRequired ?? null,
|
|
1176
|
-
tags: input.tags ?? [],
|
|
1177
|
-
data: null,
|
|
1178
|
-
});
|
|
214
|
+
return this.epicDelegate.createEpicForProject(projectId, input);
|
|
1179
215
|
}
|
|
1180
216
|
async updateEpic(id, data, expectedVersion) {
|
|
1181
|
-
|
|
1182
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1183
|
-
const now = new Date().toISOString();
|
|
1184
|
-
const current = await this.getEpic(id);
|
|
1185
|
-
if (current.version !== expectedVersion) {
|
|
1186
|
-
throw new error_types_1.OptimisticLockError('Epic', id, {
|
|
1187
|
-
expectedVersion,
|
|
1188
|
-
actualVersion: current.version,
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
if (data.parentId !== undefined) {
|
|
1192
|
-
await this.ensureValidEpicParent(current.projectId, data.parentId ?? null, id);
|
|
1193
|
-
}
|
|
1194
|
-
if (data.agentId !== undefined) {
|
|
1195
|
-
await this.ensureValidAgent(current.projectId, data.agentId ?? null);
|
|
1196
|
-
}
|
|
1197
|
-
const updateData = { ...data };
|
|
1198
|
-
if (data.data !== undefined) {
|
|
1199
|
-
updateData.data = JSON.stringify(data.data);
|
|
1200
|
-
}
|
|
1201
|
-
if (data.skillsRequired !== undefined) {
|
|
1202
|
-
updateData.skillsRequired = this.serializeSkillsRequired(data.skillsRequired);
|
|
1203
|
-
}
|
|
1204
|
-
for (const key of Object.keys(updateData)) {
|
|
1205
|
-
if (updateData[key] === undefined) {
|
|
1206
|
-
delete updateData[key];
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
await this.db
|
|
1210
|
-
.update(epics)
|
|
1211
|
-
.set({ ...updateData, version: expectedVersion + 1, updatedAt: now })
|
|
1212
|
-
.where(eq(epics.id, id));
|
|
1213
|
-
return this.getEpic(id);
|
|
217
|
+
return this.epicDelegate.updateEpic(id, data, expectedVersion);
|
|
1214
218
|
}
|
|
1215
219
|
async deleteEpic(id) {
|
|
1216
|
-
|
|
1217
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1218
|
-
const subEpics = await this.db
|
|
1219
|
-
.select({ id: epics.id })
|
|
1220
|
-
.from(epics)
|
|
1221
|
-
.where(eq(epics.parentId, id));
|
|
1222
|
-
for (const subEpic of subEpics) {
|
|
1223
|
-
await this.deleteEpic(subEpic.id);
|
|
1224
|
-
}
|
|
1225
|
-
await this.db.delete(epics).where(eq(epics.id, id));
|
|
1226
|
-
logger.info({ epicId: id, deletedSubEpics: subEpics.length }, 'Deleted epic and sub-epics');
|
|
220
|
+
return this.epicDelegate.deleteEpic(id);
|
|
1227
221
|
}
|
|
1228
222
|
async listSubEpics(parentId, options = {}) {
|
|
1229
|
-
|
|
1230
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1231
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1232
|
-
const limit = options.limit || 100;
|
|
1233
|
-
const offset = options.offset || 0;
|
|
1234
|
-
const items = await this.db
|
|
1235
|
-
.select()
|
|
1236
|
-
.from(epics)
|
|
1237
|
-
.where(eq(epics.parentId, parentId))
|
|
1238
|
-
.limit(limit)
|
|
1239
|
-
.offset(offset);
|
|
1240
|
-
const itemsWithTags = await Promise.all(items.map((item) => this.getEpic(item.id)));
|
|
1241
|
-
return {
|
|
1242
|
-
items: itemsWithTags,
|
|
1243
|
-
total: items.length,
|
|
1244
|
-
limit,
|
|
1245
|
-
offset,
|
|
1246
|
-
};
|
|
223
|
+
return this.epicDelegate.listSubEpics(parentId, options);
|
|
1247
224
|
}
|
|
1248
225
|
async listSubEpicsForParents(projectId, parentIds, options = {}) {
|
|
1249
|
-
|
|
1250
|
-
for (const parentId of parentIds) {
|
|
1251
|
-
result.set(parentId, []);
|
|
1252
|
-
}
|
|
1253
|
-
if (parentIds.length === 0) {
|
|
1254
|
-
return result;
|
|
1255
|
-
}
|
|
1256
|
-
const limitPerParent = options.limitPerParent ?? 50;
|
|
1257
|
-
const listType = (options.type ?? 'active').toLowerCase();
|
|
1258
|
-
let archivedCondition = '';
|
|
1259
|
-
if (listType === 'active') {
|
|
1260
|
-
archivedCondition = "AND lower(s.label) NOT LIKE '%archiv%'";
|
|
1261
|
-
}
|
|
1262
|
-
else if (listType === 'archived') {
|
|
1263
|
-
archivedCondition = "AND lower(s.label) LIKE '%archiv%'";
|
|
1264
|
-
}
|
|
1265
|
-
const mcpHiddenCondition = options.excludeMcpHidden ? 'AND s.mcp_hidden != 1' : '';
|
|
1266
|
-
const parentIdPlaceholders = parentIds.map(() => '?').join(', ');
|
|
1267
|
-
const queryStr = `
|
|
1268
|
-
WITH ranked AS (
|
|
1269
|
-
SELECT
|
|
1270
|
-
e.id,
|
|
1271
|
-
e.project_id,
|
|
1272
|
-
e.title,
|
|
1273
|
-
e.description,
|
|
1274
|
-
e.status_id,
|
|
1275
|
-
e.parent_id,
|
|
1276
|
-
e.agent_id,
|
|
1277
|
-
e.version,
|
|
1278
|
-
e.data,
|
|
1279
|
-
e.skills_required,
|
|
1280
|
-
e.created_at,
|
|
1281
|
-
e.updated_at,
|
|
1282
|
-
ROW_NUMBER() OVER (
|
|
1283
|
-
PARTITION BY e.parent_id
|
|
1284
|
-
ORDER BY e.updated_at DESC, e.id DESC
|
|
1285
|
-
) as row_num
|
|
1286
|
-
FROM epics e
|
|
1287
|
-
INNER JOIN statuses s ON s.id = e.status_id
|
|
1288
|
-
WHERE e.project_id = ?
|
|
1289
|
-
AND e.parent_id IN (${parentIdPlaceholders})
|
|
1290
|
-
${archivedCondition}
|
|
1291
|
-
${mcpHiddenCondition}
|
|
1292
|
-
)
|
|
1293
|
-
SELECT * FROM ranked WHERE row_num <= ?
|
|
1294
|
-
ORDER BY parent_id, row_num
|
|
1295
|
-
`;
|
|
1296
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
1297
|
-
if (!sqlite || typeof sqlite.prepare !== 'function') {
|
|
1298
|
-
throw new error_types_1.StorageError('Unable to access underlying SQLite client for sub-epic batching');
|
|
1299
|
-
}
|
|
1300
|
-
const stmt = sqlite.prepare(queryStr);
|
|
1301
|
-
const rows = stmt.all(projectId, ...parentIds, limitPerParent);
|
|
1302
|
-
const allEpics = [];
|
|
1303
|
-
for (const row of rows) {
|
|
1304
|
-
if (!row.parent_id)
|
|
1305
|
-
continue;
|
|
1306
|
-
const epic = {
|
|
1307
|
-
id: row.id,
|
|
1308
|
-
projectId: row.project_id,
|
|
1309
|
-
title: row.title,
|
|
1310
|
-
description: row.description,
|
|
1311
|
-
statusId: row.status_id,
|
|
1312
|
-
parentId: row.parent_id,
|
|
1313
|
-
agentId: row.agent_id,
|
|
1314
|
-
version: row.version,
|
|
1315
|
-
data: row.data ? JSON.parse(row.data) : null,
|
|
1316
|
-
skillsRequired: this.parseSkillsRequired(row.skills_required),
|
|
1317
|
-
tags: [],
|
|
1318
|
-
createdAt: row.created_at,
|
|
1319
|
-
updatedAt: row.updated_at,
|
|
1320
|
-
};
|
|
1321
|
-
allEpics.push(epic);
|
|
1322
|
-
const group = result.get(row.parent_id) ?? [];
|
|
1323
|
-
group.push(epic);
|
|
1324
|
-
result.set(row.parent_id, group);
|
|
1325
|
-
}
|
|
1326
|
-
if (allEpics.length > 0) {
|
|
1327
|
-
const epicIds = allEpics.map((e) => e.id);
|
|
1328
|
-
const tagsMap = await this.batchFetchTags(epicIds);
|
|
1329
|
-
for (const epic of allEpics) {
|
|
1330
|
-
epic.tags = tagsMap.get(epic.id) ?? [];
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
return result;
|
|
1334
|
-
}
|
|
1335
|
-
async batchFetchTags(epicIds) {
|
|
1336
|
-
const tagsMap = new Map();
|
|
1337
|
-
if (epicIds.length === 0) {
|
|
1338
|
-
return tagsMap;
|
|
1339
|
-
}
|
|
1340
|
-
const { epicTags, tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1341
|
-
const { eq, inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1342
|
-
const CHUNK_SIZE = 500;
|
|
1343
|
-
const chunks = [];
|
|
1344
|
-
for (let i = 0; i < epicIds.length; i += CHUNK_SIZE) {
|
|
1345
|
-
chunks.push(epicIds.slice(i, i + CHUNK_SIZE));
|
|
1346
|
-
}
|
|
1347
|
-
for (const chunk of chunks) {
|
|
1348
|
-
const rows = await this.db
|
|
1349
|
-
.select({
|
|
1350
|
-
epicId: epicTags.epicId,
|
|
1351
|
-
tagName: tags.name,
|
|
1352
|
-
})
|
|
1353
|
-
.from(epicTags)
|
|
1354
|
-
.innerJoin(tags, eq(epicTags.tagId, tags.id))
|
|
1355
|
-
.where(inArray(epicTags.epicId, chunk));
|
|
1356
|
-
for (const row of rows) {
|
|
1357
|
-
const existing = tagsMap.get(row.epicId) ?? [];
|
|
1358
|
-
existing.push(row.tagName);
|
|
1359
|
-
tagsMap.set(row.epicId, existing);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
return tagsMap;
|
|
226
|
+
return this.epicDelegate.listSubEpicsForParents(projectId, parentIds, options);
|
|
1363
227
|
}
|
|
1364
228
|
async countSubEpicsByStatus(parentId) {
|
|
1365
|
-
|
|
1366
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1367
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1368
|
-
const rows = await this.db
|
|
1369
|
-
.select({ statusId: epics.statusId })
|
|
1370
|
-
.from(epics)
|
|
1371
|
-
.where(eq(epics.parentId, parentId));
|
|
1372
|
-
return rows.reduce((acc, row) => {
|
|
1373
|
-
const key = row.statusId;
|
|
1374
|
-
acc[key] = (acc[key] ?? 0) + 1;
|
|
1375
|
-
return acc;
|
|
1376
|
-
}, {});
|
|
229
|
+
return this.epicDelegate.countSubEpicsByStatus(parentId);
|
|
1377
230
|
}
|
|
1378
231
|
async countEpicsByStatus(statusId) {
|
|
1379
|
-
|
|
1380
|
-
const { eq, count } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1381
|
-
const result = await this.db
|
|
1382
|
-
.select({ count: count() })
|
|
1383
|
-
.from(epics)
|
|
1384
|
-
.where(eq(epics.statusId, statusId));
|
|
1385
|
-
return Number(result[0]?.count ?? 0);
|
|
232
|
+
return this.epicDelegate.countEpicsByStatus(statusId);
|
|
1386
233
|
}
|
|
1387
234
|
async updateEpicsStatus(oldStatusId, newStatusId) {
|
|
1388
|
-
|
|
1389
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1390
|
-
const now = new Date().toISOString();
|
|
1391
|
-
const result = await this.db
|
|
1392
|
-
.update(epics)
|
|
1393
|
-
.set({ statusId: newStatusId, updatedAt: now })
|
|
1394
|
-
.where(eq(epics.statusId, oldStatusId));
|
|
1395
|
-
return result.changes ?? 0;
|
|
235
|
+
return this.epicDelegate.updateEpicsStatus(oldStatusId, newStatusId);
|
|
1396
236
|
}
|
|
1397
237
|
async listEpicComments(epicId, options = {}) {
|
|
1398
|
-
|
|
1399
|
-
const { epicComments } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1400
|
-
const { eq, asc, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1401
|
-
const limit = options.limit || 100;
|
|
1402
|
-
const offset = options.offset || 0;
|
|
1403
|
-
const items = await this.db
|
|
1404
|
-
.select()
|
|
1405
|
-
.from(epicComments)
|
|
1406
|
-
.where(eq(epicComments.epicId, epicId))
|
|
1407
|
-
.orderBy(asc(epicComments.createdAt))
|
|
1408
|
-
.limit(limit)
|
|
1409
|
-
.offset(offset);
|
|
1410
|
-
const totalResult = await this.db
|
|
1411
|
-
.select({ count: sql `count(*)` })
|
|
1412
|
-
.from(epicComments)
|
|
1413
|
-
.where(eq(epicComments.epicId, epicId));
|
|
1414
|
-
const total = Number(totalResult[0]?.count ?? 0);
|
|
1415
|
-
return {
|
|
1416
|
-
items: items,
|
|
1417
|
-
total,
|
|
1418
|
-
limit,
|
|
1419
|
-
offset,
|
|
1420
|
-
};
|
|
238
|
+
return this.epicDelegate.listEpicComments(epicId, options);
|
|
1421
239
|
}
|
|
1422
240
|
async createEpicComment(data) {
|
|
1423
|
-
|
|
1424
|
-
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
1425
|
-
const { epicComments } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1426
|
-
const now = new Date().toISOString();
|
|
1427
|
-
const comment = {
|
|
1428
|
-
id: randomUUID(),
|
|
1429
|
-
epicId: data.epicId,
|
|
1430
|
-
authorName: data.authorName,
|
|
1431
|
-
content: data.content,
|
|
1432
|
-
createdAt: now,
|
|
1433
|
-
updatedAt: now,
|
|
1434
|
-
};
|
|
1435
|
-
await this.db.insert(epicComments).values({
|
|
1436
|
-
id: comment.id,
|
|
1437
|
-
epicId: comment.epicId,
|
|
1438
|
-
authorName: comment.authorName,
|
|
1439
|
-
content: comment.content,
|
|
1440
|
-
createdAt: comment.createdAt,
|
|
1441
|
-
updatedAt: comment.updatedAt,
|
|
1442
|
-
});
|
|
1443
|
-
return comment;
|
|
241
|
+
return this.epicDelegate.createEpicComment(data);
|
|
1444
242
|
}
|
|
1445
243
|
async deleteEpicComment(id) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
244
|
+
return this.epicDelegate.deleteEpicComment(id);
|
|
245
|
+
}
|
|
246
|
+
async getEpicsByIdPrefix(projectId, prefix) {
|
|
247
|
+
return this.epicDelegate.getEpicsByIdPrefix(projectId, prefix);
|
|
1449
248
|
}
|
|
1450
249
|
async createPrompt(data) {
|
|
1451
|
-
|
|
1452
|
-
const now = new Date().toISOString();
|
|
1453
|
-
const { prompts, promptTags, tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1454
|
-
const prompt = {
|
|
1455
|
-
id: randomUUID(),
|
|
1456
|
-
...data,
|
|
1457
|
-
version: 1,
|
|
1458
|
-
createdAt: now,
|
|
1459
|
-
updatedAt: now,
|
|
1460
|
-
};
|
|
1461
|
-
await this.db.insert(prompts).values({
|
|
1462
|
-
id: prompt.id,
|
|
1463
|
-
projectId: prompt.projectId,
|
|
1464
|
-
title: prompt.title,
|
|
1465
|
-
content: prompt.content,
|
|
1466
|
-
version: prompt.version,
|
|
1467
|
-
createdAt: prompt.createdAt,
|
|
1468
|
-
updatedAt: prompt.updatedAt,
|
|
1469
|
-
});
|
|
1470
|
-
if (data.tags?.length) {
|
|
1471
|
-
for (const tagName of data.tags) {
|
|
1472
|
-
const { eq, and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1473
|
-
let tag = await this.db
|
|
1474
|
-
.select()
|
|
1475
|
-
.from(tags)
|
|
1476
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, data.projectId || ''), isNull(tags.projectId))))
|
|
1477
|
-
.limit(1);
|
|
1478
|
-
if (!tag[0]) {
|
|
1479
|
-
const newTag = await this.createTag({ projectId: data.projectId, name: tagName });
|
|
1480
|
-
tag = [newTag];
|
|
1481
|
-
}
|
|
1482
|
-
await this.db.insert(promptTags).values({
|
|
1483
|
-
promptId: prompt.id,
|
|
1484
|
-
tagId: tag[0].id,
|
|
1485
|
-
createdAt: now,
|
|
1486
|
-
});
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return prompt;
|
|
250
|
+
return this.promptDelegate.createPrompt(data);
|
|
1490
251
|
}
|
|
1491
252
|
async getPrompt(id) {
|
|
1492
|
-
|
|
1493
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1494
|
-
const result = await this.db.select().from(prompts).where(eq(prompts.id, id)).limit(1);
|
|
1495
|
-
if (!result[0]) {
|
|
1496
|
-
throw new error_types_1.NotFoundError('Prompt', id);
|
|
1497
|
-
}
|
|
1498
|
-
const promptTagsResult = await this.db
|
|
1499
|
-
.select({ tag: tags })
|
|
1500
|
-
.from(promptTags)
|
|
1501
|
-
.innerJoin(tags, eq(promptTags.tagId, tags.id))
|
|
1502
|
-
.where(eq(promptTags.promptId, id));
|
|
1503
|
-
return {
|
|
1504
|
-
...result[0],
|
|
1505
|
-
tags: promptTagsResult.map((pt) => pt.tag.name),
|
|
1506
|
-
};
|
|
253
|
+
return this.promptDelegate.getPrompt(id);
|
|
1507
254
|
}
|
|
1508
255
|
async listPrompts(filters = {}) {
|
|
1509
|
-
|
|
1510
|
-
const { and, eq, isNull, asc, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1511
|
-
const whereClauses = [];
|
|
1512
|
-
if (filters.projectId !== undefined) {
|
|
1513
|
-
whereClauses.push(filters.projectId === null
|
|
1514
|
-
? isNull(prompts.projectId)
|
|
1515
|
-
: eq(prompts.projectId, filters.projectId));
|
|
1516
|
-
}
|
|
1517
|
-
const searchTerm = filters.q?.trim();
|
|
1518
|
-
if (searchTerm) {
|
|
1519
|
-
const pattern = `%${searchTerm.toLowerCase()}%`;
|
|
1520
|
-
whereClauses.push(sql `lower(${prompts.title}) LIKE ${pattern}`);
|
|
1521
|
-
}
|
|
1522
|
-
const whereCondition = whereClauses.length === 0
|
|
1523
|
-
? undefined
|
|
1524
|
-
: whereClauses.length === 1
|
|
1525
|
-
? whereClauses[0]
|
|
1526
|
-
: and(...whereClauses);
|
|
1527
|
-
const selectFields = {
|
|
1528
|
-
id: prompts.id,
|
|
1529
|
-
projectId: prompts.projectId,
|
|
1530
|
-
title: prompts.title,
|
|
1531
|
-
content: prompts.content,
|
|
1532
|
-
version: prompts.version,
|
|
1533
|
-
createdAt: prompts.createdAt,
|
|
1534
|
-
updatedAt: prompts.updatedAt,
|
|
1535
|
-
};
|
|
1536
|
-
const rows = await (whereCondition
|
|
1537
|
-
? this.db.select(selectFields).from(prompts).where(whereCondition).orderBy(asc(prompts.title))
|
|
1538
|
-
: this.db.select(selectFields).from(prompts).orderBy(asc(prompts.title)));
|
|
1539
|
-
if (!rows.length) {
|
|
1540
|
-
return {
|
|
1541
|
-
items: [],
|
|
1542
|
-
total: 0,
|
|
1543
|
-
limit: filters.limit ?? 100,
|
|
1544
|
-
offset: filters.offset ?? 0,
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
const PREVIEW_LENGTH = 200;
|
|
1548
|
-
const promptsWithTags = await Promise.all(rows.map(async (row) => {
|
|
1549
|
-
const tagRows = await this.db
|
|
1550
|
-
.select({ tagName: tags.name })
|
|
1551
|
-
.from(promptTags)
|
|
1552
|
-
.innerJoin(tags, eq(tags.id, promptTags.tagId))
|
|
1553
|
-
.where(eq(promptTags.promptId, row.id));
|
|
1554
|
-
const content = row.content ?? '';
|
|
1555
|
-
const contentPreview = content.length > PREVIEW_LENGTH ? content.slice(0, PREVIEW_LENGTH) + '…' : content;
|
|
1556
|
-
return {
|
|
1557
|
-
id: row.id,
|
|
1558
|
-
projectId: row.projectId,
|
|
1559
|
-
title: row.title,
|
|
1560
|
-
contentPreview,
|
|
1561
|
-
version: row.version,
|
|
1562
|
-
tags: tagRows.map((t) => t.tagName),
|
|
1563
|
-
createdAt: row.createdAt,
|
|
1564
|
-
updatedAt: row.updatedAt,
|
|
1565
|
-
};
|
|
1566
|
-
}));
|
|
1567
|
-
const limit = filters.limit ?? 100;
|
|
1568
|
-
const offset = filters.offset ?? 0;
|
|
1569
|
-
const items = promptsWithTags.slice(offset, offset + limit);
|
|
1570
|
-
return {
|
|
1571
|
-
items,
|
|
1572
|
-
total: promptsWithTags.length,
|
|
1573
|
-
limit,
|
|
1574
|
-
offset,
|
|
1575
|
-
};
|
|
256
|
+
return this.promptDelegate.listPrompts(filters);
|
|
1576
257
|
}
|
|
1577
258
|
async updatePrompt(id, data, expectedVersion) {
|
|
1578
|
-
|
|
1579
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1580
|
-
const now = new Date().toISOString();
|
|
1581
|
-
logger.info({ id, data, expectedVersion }, 'updatePrompt called with data');
|
|
1582
|
-
const current = await this.getPrompt(id);
|
|
1583
|
-
if (current.version !== expectedVersion) {
|
|
1584
|
-
throw new error_types_1.OptimisticLockError('Prompt', id, {
|
|
1585
|
-
expectedVersion,
|
|
1586
|
-
actualVersion: current.version,
|
|
1587
|
-
});
|
|
1588
|
-
}
|
|
1589
|
-
const { tags: newTags, ...updateData } = data;
|
|
1590
|
-
logger.info({ newTags, updateData }, 'Separated tags from updateData');
|
|
1591
|
-
await this.db
|
|
1592
|
-
.update(prompts)
|
|
1593
|
-
.set({ ...updateData, version: expectedVersion + 1, updatedAt: now })
|
|
1594
|
-
.where(eq(prompts.id, id));
|
|
1595
|
-
logger.info('Updated prompt fields in database');
|
|
1596
|
-
if (newTags !== undefined) {
|
|
1597
|
-
logger.info({ newTags, newTagsLength: newTags.length }, 'Updating tags');
|
|
1598
|
-
await this.db.delete(promptTags).where(eq(promptTags.promptId, id));
|
|
1599
|
-
logger.info('Deleted existing prompt tags');
|
|
1600
|
-
if (newTags.length > 0) {
|
|
1601
|
-
for (const tagName of newTags) {
|
|
1602
|
-
logger.info({ tagName }, 'Processing tag');
|
|
1603
|
-
const { and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1604
|
-
let tag = await this.db
|
|
1605
|
-
.select()
|
|
1606
|
-
.from(tags)
|
|
1607
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, current.projectId || ''), isNull(tags.projectId))))
|
|
1608
|
-
.limit(1);
|
|
1609
|
-
if (!tag[0]) {
|
|
1610
|
-
logger.info({ tagName }, 'Tag not found, creating new tag');
|
|
1611
|
-
const newTag = await this.createTag({ projectId: current.projectId, name: tagName });
|
|
1612
|
-
tag = [newTag];
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1615
|
-
logger.info({ tagName, tagId: tag[0].id }, 'Found existing tag');
|
|
1616
|
-
}
|
|
1617
|
-
await this.db.insert(promptTags).values({
|
|
1618
|
-
promptId: id,
|
|
1619
|
-
tagId: tag[0].id,
|
|
1620
|
-
createdAt: now,
|
|
1621
|
-
});
|
|
1622
|
-
logger.info({ tagName, tagId: tag[0].id }, 'Inserted prompt tag');
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
else {
|
|
1627
|
-
logger.info('No tags provided in update data');
|
|
1628
|
-
}
|
|
1629
|
-
return this.getPrompt(id);
|
|
259
|
+
return this.promptDelegate.updatePrompt(id, data, expectedVersion);
|
|
1630
260
|
}
|
|
1631
261
|
async deletePrompt(id) {
|
|
1632
|
-
|
|
1633
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1634
|
-
await this.db.delete(prompts).where(eq(prompts.id, id));
|
|
262
|
+
return this.promptDelegate.deletePrompt(id);
|
|
1635
263
|
}
|
|
1636
264
|
async getInitialSessionPrompt(projectId) {
|
|
1637
|
-
|
|
1638
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1639
|
-
let rawValue;
|
|
1640
|
-
try {
|
|
1641
|
-
const mapRows = await this.db
|
|
1642
|
-
.select({ value: settings.value })
|
|
1643
|
-
.from(settings)
|
|
1644
|
-
.where(eq(settings.key, 'initialSessionPromptIds'))
|
|
1645
|
-
.limit(1);
|
|
1646
|
-
const mapRaw = mapRows[0]?.value;
|
|
1647
|
-
const promptIdFromMap = this.extractPromptIdFromMap(mapRaw, projectId);
|
|
1648
|
-
if (promptIdFromMap) {
|
|
1649
|
-
rawValue = promptIdFromMap;
|
|
1650
|
-
}
|
|
1651
|
-
else {
|
|
1652
|
-
const result = await this.db
|
|
1653
|
-
.select({ value: settings.value })
|
|
1654
|
-
.from(settings)
|
|
1655
|
-
.where(eq(settings.key, 'initialSessionPromptId'))
|
|
1656
|
-
.limit(1);
|
|
1657
|
-
rawValue = result[0]?.value;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
catch (error) {
|
|
1661
|
-
logger.warn({ error }, 'Drizzle read failed for initialSessionPromptId; falling back to raw SQLite');
|
|
1662
|
-
try {
|
|
1663
|
-
const mapRow = (0, sqlite_raw_1.getRawSqliteClient)(this.db)
|
|
1664
|
-
.prepare('SELECT value FROM settings WHERE key = ? LIMIT 1')
|
|
1665
|
-
.get('initialSessionPromptIds');
|
|
1666
|
-
const fromMap = this.extractPromptIdFromMap(mapRow?.value, projectId);
|
|
1667
|
-
if (fromMap) {
|
|
1668
|
-
rawValue = fromMap;
|
|
1669
|
-
}
|
|
1670
|
-
else {
|
|
1671
|
-
const row = (0, sqlite_raw_1.getRawSqliteClient)(this.db)
|
|
1672
|
-
.prepare('SELECT value FROM settings WHERE key = ? LIMIT 1')
|
|
1673
|
-
.get('initialSessionPromptId');
|
|
1674
|
-
rawValue = row?.value;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
catch (sqliteError) {
|
|
1678
|
-
logger.error({ sqliteError }, 'Raw SQLite read failed for initialSessionPromptId');
|
|
1679
|
-
return null;
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
const promptId = typeof rawValue === 'string' ? rawValue : this.extractPromptId(rawValue);
|
|
1683
|
-
logger.debug({ rawType: typeof rawValue, rawValue: safePreview(rawValue), promptId }, 'Resolved initial session prompt id from settings');
|
|
1684
|
-
if (!promptId) {
|
|
1685
|
-
return null;
|
|
1686
|
-
}
|
|
1687
|
-
try {
|
|
1688
|
-
return await this.getPrompt(promptId);
|
|
1689
|
-
}
|
|
1690
|
-
catch (error) {
|
|
1691
|
-
if (error instanceof error_types_1.NotFoundError) {
|
|
1692
|
-
logger.warn({ promptId }, 'Initial session prompt not found');
|
|
1693
|
-
return null;
|
|
1694
|
-
}
|
|
1695
|
-
throw error;
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
extractPromptId(value) {
|
|
1699
|
-
if (value === undefined || value === null) {
|
|
1700
|
-
return null;
|
|
1701
|
-
}
|
|
1702
|
-
if (typeof value === 'object') {
|
|
1703
|
-
if ('initialSessionPromptId' in value) {
|
|
1704
|
-
return this.extractPromptId(value.initialSessionPromptId);
|
|
1705
|
-
}
|
|
1706
|
-
if ('value' in value) {
|
|
1707
|
-
return this.extractPromptId(value.value);
|
|
1708
|
-
}
|
|
1709
|
-
return null;
|
|
1710
|
-
}
|
|
1711
|
-
if (typeof value === 'string') {
|
|
1712
|
-
const trimmed = value.trim();
|
|
1713
|
-
if (!trimmed) {
|
|
1714
|
-
return null;
|
|
1715
|
-
}
|
|
1716
|
-
try {
|
|
1717
|
-
const parsed = JSON.parse(trimmed);
|
|
1718
|
-
return this.extractPromptId(parsed);
|
|
1719
|
-
}
|
|
1720
|
-
catch {
|
|
1721
|
-
}
|
|
1722
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
1723
|
-
return trimmed.slice(1, -1).trim() || null;
|
|
1724
|
-
}
|
|
1725
|
-
return trimmed;
|
|
1726
|
-
}
|
|
1727
|
-
return String(value).trim() || null;
|
|
1728
|
-
}
|
|
1729
|
-
extractPromptIdFromMap(value, projectId) {
|
|
1730
|
-
try {
|
|
1731
|
-
const obj = typeof value === 'string' ? JSON.parse(value) : value;
|
|
1732
|
-
if (obj && typeof obj === 'object') {
|
|
1733
|
-
const map = obj;
|
|
1734
|
-
if (projectId && typeof map[projectId] === 'string') {
|
|
1735
|
-
const v = map[projectId].trim();
|
|
1736
|
-
return v || null;
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
catch {
|
|
1741
|
-
}
|
|
1742
|
-
return null;
|
|
265
|
+
return this.promptDelegate.getInitialSessionPrompt(projectId);
|
|
1743
266
|
}
|
|
1744
267
|
async listDocuments(filters = {}) {
|
|
1745
|
-
|
|
1746
|
-
const { and, eq, isNull, like, or, desc, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1747
|
-
const whereClauses = [];
|
|
1748
|
-
if (filters.projectId !== undefined) {
|
|
1749
|
-
whereClauses.push(filters.projectId === null
|
|
1750
|
-
? isNull(documents.projectId)
|
|
1751
|
-
: eq(documents.projectId, filters.projectId));
|
|
1752
|
-
}
|
|
1753
|
-
const tagKeys = this.normalizeTagList(filters.tagKeys);
|
|
1754
|
-
if (tagKeys.length) {
|
|
1755
|
-
for (const key of tagKeys) {
|
|
1756
|
-
whereClauses.push(sql `EXISTS (
|
|
1757
|
-
SELECT 1
|
|
1758
|
-
FROM ${documentTags} dt
|
|
1759
|
-
INNER JOIN ${tags} t ON t.id = dt.tag_id
|
|
1760
|
-
WHERE dt.document_id = ${documents.id}
|
|
1761
|
-
AND (
|
|
1762
|
-
(
|
|
1763
|
-
CASE
|
|
1764
|
-
WHEN instr(t.name, ':') > 0 THEN substr(t.name, 1, instr(t.name, ':') - 1)
|
|
1765
|
-
ELSE NULL
|
|
1766
|
-
END
|
|
1767
|
-
) = ${key}
|
|
1768
|
-
OR t.name = ${key}
|
|
1769
|
-
)
|
|
1770
|
-
)`);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
const searchTerm = filters.q?.trim();
|
|
1774
|
-
if (searchTerm) {
|
|
1775
|
-
const pattern = `%${searchTerm}%`;
|
|
1776
|
-
whereClauses.push(or(like(documents.title, pattern), like(documents.contentMd, pattern)));
|
|
1777
|
-
}
|
|
1778
|
-
const whereCondition = whereClauses.length === 0
|
|
1779
|
-
? undefined
|
|
1780
|
-
: whereClauses.length === 1
|
|
1781
|
-
? whereClauses[0]
|
|
1782
|
-
: and(...whereClauses);
|
|
1783
|
-
const rows = await (whereCondition
|
|
1784
|
-
? this.db.select().from(documents).where(whereCondition).orderBy(desc(documents.updatedAt))
|
|
1785
|
-
: this.db.select().from(documents).orderBy(desc(documents.updatedAt)));
|
|
1786
|
-
if (!rows.length) {
|
|
1787
|
-
return {
|
|
1788
|
-
items: [],
|
|
1789
|
-
total: 0,
|
|
1790
|
-
limit: filters.limit ?? 100,
|
|
1791
|
-
offset: filters.offset ?? 0,
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
const documentsWithTags = await Promise.all(rows.map((row) => this.getDocument({ id: row.id })));
|
|
1795
|
-
let filtered = documentsWithTags;
|
|
1796
|
-
if (filters.tags?.length) {
|
|
1797
|
-
const requiredTags = this.normalizeTagList(filters.tags);
|
|
1798
|
-
filtered = documentsWithTags.filter((doc) => requiredTags.every((tag) => doc.tags.includes(tag)));
|
|
1799
|
-
}
|
|
1800
|
-
const limit = filters.limit ?? 100;
|
|
1801
|
-
const offset = filters.offset ?? 0;
|
|
1802
|
-
const items = filtered.slice(offset, offset + limit);
|
|
1803
|
-
return {
|
|
1804
|
-
items,
|
|
1805
|
-
total: filtered.length,
|
|
1806
|
-
limit,
|
|
1807
|
-
offset,
|
|
1808
|
-
};
|
|
268
|
+
return this.documentDelegate.listDocuments(filters);
|
|
1809
269
|
}
|
|
1810
270
|
async getDocument(identifier) {
|
|
1811
|
-
|
|
1812
|
-
const { eq, and, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1813
|
-
let whereCondition;
|
|
1814
|
-
if (identifier.id) {
|
|
1815
|
-
whereCondition = eq(documents.id, identifier.id);
|
|
1816
|
-
}
|
|
1817
|
-
else if (identifier.slug) {
|
|
1818
|
-
if (identifier.projectId === undefined) {
|
|
1819
|
-
throw new error_types_1.ValidationError('projectId is required when querying document by slug');
|
|
1820
|
-
}
|
|
1821
|
-
whereCondition =
|
|
1822
|
-
identifier.projectId === null
|
|
1823
|
-
? and(isNull(documents.projectId), eq(documents.slug, identifier.slug))
|
|
1824
|
-
: and(eq(documents.projectId, identifier.projectId), eq(documents.slug, identifier.slug));
|
|
1825
|
-
}
|
|
1826
|
-
else {
|
|
1827
|
-
throw new error_types_1.ValidationError('Document identifier requires either id or slug');
|
|
1828
|
-
}
|
|
1829
|
-
const result = await this.db.select().from(documents).where(whereCondition).limit(1);
|
|
1830
|
-
const record = result[0];
|
|
1831
|
-
if (!record) {
|
|
1832
|
-
const lookup = identifier.id ?? `${identifier.projectId ?? 'global'}:${identifier.slug}`;
|
|
1833
|
-
throw new error_types_1.NotFoundError('Document', lookup || 'unknown');
|
|
1834
|
-
}
|
|
1835
|
-
const tagRows = await this.db
|
|
1836
|
-
.select({ tag: tags })
|
|
1837
|
-
.from(documentTags)
|
|
1838
|
-
.innerJoin(tags, eq(documentTags.tagId, tags.id))
|
|
1839
|
-
.where(eq(documentTags.documentId, record.id));
|
|
1840
|
-
return {
|
|
1841
|
-
...record,
|
|
1842
|
-
projectId: record.projectId ?? null,
|
|
1843
|
-
tags: tagRows.map((row) => row.tag.name),
|
|
1844
|
-
};
|
|
271
|
+
return this.documentDelegate.getDocument(identifier);
|
|
1845
272
|
}
|
|
1846
273
|
async createDocument(data) {
|
|
1847
|
-
|
|
1848
|
-
const now = new Date().toISOString();
|
|
1849
|
-
const normalizedProjectId = data.projectId ?? null;
|
|
1850
|
-
const { documents } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1851
|
-
const slugSource = data.slug ?? data.title;
|
|
1852
|
-
const slug = await this.generateDocumentSlug(normalizedProjectId, slugSource);
|
|
1853
|
-
const tags = this.normalizeTagList(data.tags);
|
|
1854
|
-
const id = randomUUID();
|
|
1855
|
-
await this.db.insert(documents).values({
|
|
1856
|
-
id,
|
|
1857
|
-
projectId: normalizedProjectId,
|
|
1858
|
-
title: data.title,
|
|
1859
|
-
slug,
|
|
1860
|
-
contentMd: data.contentMd,
|
|
1861
|
-
version: 1,
|
|
1862
|
-
archived: false,
|
|
1863
|
-
createdAt: now,
|
|
1864
|
-
updatedAt: now,
|
|
1865
|
-
});
|
|
1866
|
-
if (tags.length) {
|
|
1867
|
-
await this.setDocumentTags(id, tags, normalizedProjectId);
|
|
1868
|
-
}
|
|
1869
|
-
logger.info({ documentId: id }, 'Created document');
|
|
1870
|
-
return this.getDocument({ id });
|
|
274
|
+
return this.documentDelegate.createDocument(data);
|
|
1871
275
|
}
|
|
1872
276
|
async updateDocument(id, data) {
|
|
1873
|
-
|
|
1874
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1875
|
-
const now = new Date().toISOString();
|
|
1876
|
-
const current = await this.getDocument({ id });
|
|
1877
|
-
if (data.version !== undefined && data.version !== current.version) {
|
|
1878
|
-
throw new error_types_1.OptimisticLockError('Document', id, {
|
|
1879
|
-
expectedVersion: data.version,
|
|
1880
|
-
actualVersion: current.version,
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
|
-
const updatePayload = {
|
|
1884
|
-
updatedAt: now,
|
|
1885
|
-
version: current.version + 1,
|
|
1886
|
-
};
|
|
1887
|
-
if (data.title !== undefined) {
|
|
1888
|
-
updatePayload.title = data.title;
|
|
1889
|
-
}
|
|
1890
|
-
if (data.contentMd !== undefined) {
|
|
1891
|
-
updatePayload.contentMd = data.contentMd;
|
|
1892
|
-
}
|
|
1893
|
-
if (data.archived !== undefined) {
|
|
1894
|
-
updatePayload.archived = data.archived;
|
|
1895
|
-
}
|
|
1896
|
-
if (data.slug !== undefined) {
|
|
1897
|
-
updatePayload.slug = await this.generateDocumentSlug(current.projectId, data.slug, id);
|
|
1898
|
-
}
|
|
1899
|
-
await this.db.update(documents).set(updatePayload).where(eq(documents.id, id));
|
|
1900
|
-
if (data.tags !== undefined) {
|
|
1901
|
-
const tags = this.normalizeTagList(data.tags);
|
|
1902
|
-
await this.setDocumentTags(id, tags, current.projectId);
|
|
1903
|
-
}
|
|
1904
|
-
logger.info({ documentId: id }, 'Updated document');
|
|
1905
|
-
return this.getDocument({ id });
|
|
277
|
+
return this.documentDelegate.updateDocument(id, data);
|
|
1906
278
|
}
|
|
1907
279
|
async deleteDocument(id) {
|
|
1908
|
-
|
|
1909
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1910
|
-
await this.db.delete(documents).where(eq(documents.id, id));
|
|
1911
|
-
logger.info({ documentId: id }, 'Deleted document');
|
|
280
|
+
return this.documentDelegate.deleteDocument(id);
|
|
1912
281
|
}
|
|
1913
282
|
async createTag(data) {
|
|
1914
|
-
|
|
1915
|
-
const now = new Date().toISOString();
|
|
1916
|
-
const { tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
1917
|
-
const tag = {
|
|
1918
|
-
id: randomUUID(),
|
|
1919
|
-
...data,
|
|
1920
|
-
createdAt: now,
|
|
1921
|
-
updatedAt: now,
|
|
1922
|
-
};
|
|
1923
|
-
await this.db.insert(tags).values({
|
|
1924
|
-
id: tag.id,
|
|
1925
|
-
projectId: tag.projectId,
|
|
1926
|
-
name: tag.name,
|
|
1927
|
-
createdAt: tag.createdAt,
|
|
1928
|
-
updatedAt: tag.updatedAt,
|
|
1929
|
-
});
|
|
1930
|
-
return tag;
|
|
283
|
+
return this.tagDelegate.createTag(data);
|
|
1931
284
|
}
|
|
1932
285
|
async getTag(id) {
|
|
1933
|
-
|
|
1934
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1935
|
-
const result = await this.db.select().from(tags).where(eq(tags.id, id)).limit(1);
|
|
1936
|
-
if (!result[0]) {
|
|
1937
|
-
throw new error_types_1.NotFoundError('Tag', id);
|
|
1938
|
-
}
|
|
1939
|
-
return result[0];
|
|
286
|
+
return this.tagDelegate.getTag(id);
|
|
1940
287
|
}
|
|
1941
288
|
async listTags(projectId, options = {}) {
|
|
1942
|
-
|
|
1943
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1944
|
-
const limit = options.limit || 100;
|
|
1945
|
-
const offset = options.offset || 0;
|
|
1946
|
-
const query = projectId ? eq(tags.projectId, projectId) : undefined;
|
|
1947
|
-
const items = await this.db.select().from(tags).where(query).limit(limit).offset(offset);
|
|
1948
|
-
return {
|
|
1949
|
-
items: items,
|
|
1950
|
-
total: items.length,
|
|
1951
|
-
limit,
|
|
1952
|
-
offset,
|
|
1953
|
-
};
|
|
289
|
+
return this.tagDelegate.listTags(projectId, options);
|
|
1954
290
|
}
|
|
1955
291
|
async updateTag(id, data) {
|
|
1956
|
-
|
|
1957
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1958
|
-
const now = new Date().toISOString();
|
|
1959
|
-
await this.db
|
|
1960
|
-
.update(tags)
|
|
1961
|
-
.set({ ...data, updatedAt: now })
|
|
1962
|
-
.where(eq(tags.id, id));
|
|
1963
|
-
return this.getTag(id);
|
|
292
|
+
return this.tagDelegate.updateTag(id, data);
|
|
1964
293
|
}
|
|
1965
294
|
async deleteTag(id) {
|
|
1966
|
-
|
|
1967
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
1968
|
-
await this.db.delete(tags).where(eq(tags.id, id));
|
|
295
|
+
return this.tagDelegate.deleteTag(id);
|
|
1969
296
|
}
|
|
1970
297
|
async createProvider(data) {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
createdAt: now,
|
|
1988
|
-
updatedAt: now,
|
|
1989
|
-
};
|
|
1990
|
-
await this.db.insert(providers).values({
|
|
1991
|
-
id: provider.id,
|
|
1992
|
-
name: provider.name,
|
|
1993
|
-
binPath: provider.binPath,
|
|
1994
|
-
mcpConfigured: provider.mcpConfigured,
|
|
1995
|
-
mcpEndpoint: provider.mcpEndpoint,
|
|
1996
|
-
mcpRegisteredAt: provider.mcpRegisteredAt,
|
|
1997
|
-
autoCompactThreshold: provider.autoCompactThreshold,
|
|
1998
|
-
createdAt: provider.createdAt,
|
|
1999
|
-
updatedAt: provider.updatedAt,
|
|
2000
|
-
});
|
|
2001
|
-
logger.info({ providerId: provider.id, name: provider.name }, 'Created provider');
|
|
2002
|
-
return provider;
|
|
298
|
+
return this.providerDelegate.createProvider(data);
|
|
299
|
+
}
|
|
300
|
+
async createProviderModel(data) {
|
|
301
|
+
return this.providerModelDelegate.createProviderModel(data);
|
|
302
|
+
}
|
|
303
|
+
async listProviderModelsByProvider(providerId) {
|
|
304
|
+
return this.providerModelDelegate.listProviderModelsByProvider(providerId);
|
|
305
|
+
}
|
|
306
|
+
async listProviderModelsByProviderIds(providerIds) {
|
|
307
|
+
return this.providerModelDelegate.listProviderModelsByProviderIds(providerIds);
|
|
308
|
+
}
|
|
309
|
+
async deleteProviderModel(id) {
|
|
310
|
+
return this.providerModelDelegate.deleteProviderModel(id);
|
|
311
|
+
}
|
|
312
|
+
async bulkCreateProviderModels(providerId, names) {
|
|
313
|
+
return this.providerModelDelegate.bulkCreateProviderModels(providerId, names);
|
|
2003
314
|
}
|
|
2004
315
|
async getProvider(id) {
|
|
2005
|
-
|
|
2006
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2007
|
-
const result = await this.db.select().from(providers).where(eq(providers.id, id)).limit(1);
|
|
2008
|
-
if (!result[0]) {
|
|
2009
|
-
throw new error_types_1.NotFoundError('Provider', id);
|
|
2010
|
-
}
|
|
2011
|
-
return result[0];
|
|
316
|
+
return this.providerDelegate.getProvider(id);
|
|
2012
317
|
}
|
|
2013
318
|
async listProviders(options = {}) {
|
|
2014
|
-
|
|
2015
|
-
const limit = options.limit || 100;
|
|
2016
|
-
const offset = options.offset || 0;
|
|
2017
|
-
const items = await this.db.select().from(providers).limit(limit).offset(offset);
|
|
2018
|
-
return {
|
|
2019
|
-
items: items,
|
|
2020
|
-
total: items.length,
|
|
2021
|
-
limit,
|
|
2022
|
-
offset,
|
|
2023
|
-
};
|
|
319
|
+
return this.providerDelegate.listProviders(options);
|
|
2024
320
|
}
|
|
2025
321
|
async listProvidersByIds(ids) {
|
|
2026
|
-
|
|
2027
|
-
return [];
|
|
2028
|
-
}
|
|
2029
|
-
const { providers } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2030
|
-
const { inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2031
|
-
const results = await this.db.select().from(providers).where(inArray(providers.id, ids));
|
|
2032
|
-
return results;
|
|
322
|
+
return this.providerDelegate.listProvidersByIds(ids);
|
|
2033
323
|
}
|
|
2034
324
|
async updateProvider(id, data) {
|
|
2035
|
-
|
|
2036
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2037
|
-
const now = new Date().toISOString();
|
|
2038
|
-
const payload = Object.fromEntries(Object.entries({
|
|
2039
|
-
...data,
|
|
2040
|
-
updatedAt: now,
|
|
2041
|
-
}).filter(([, value]) => value !== undefined));
|
|
2042
|
-
await this.db.update(providers).set(payload).where(eq(providers.id, id));
|
|
2043
|
-
logger.info({ providerId: id }, 'Updated provider');
|
|
2044
|
-
return this.getProvider(id);
|
|
325
|
+
return this.providerDelegate.updateProvider(id, data);
|
|
2045
326
|
}
|
|
2046
327
|
async deleteProvider(id) {
|
|
2047
|
-
|
|
2048
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2049
|
-
await this.db.delete(providers).where(eq(providers.id, id));
|
|
2050
|
-
logger.info({ providerId: id }, 'Deleted provider');
|
|
328
|
+
return this.providerDelegate.deleteProvider(id);
|
|
2051
329
|
}
|
|
2052
330
|
async getProviderMcpMetadata(id) {
|
|
2053
|
-
|
|
2054
|
-
return {
|
|
2055
|
-
mcpConfigured: provider.mcpConfigured,
|
|
2056
|
-
mcpEndpoint: provider.mcpEndpoint,
|
|
2057
|
-
mcpRegisteredAt: provider.mcpRegisteredAt,
|
|
2058
|
-
};
|
|
331
|
+
return this.providerDelegate.getProviderMcpMetadata(id);
|
|
2059
332
|
}
|
|
2060
333
|
async updateProviderMcpMetadata(id, metadata) {
|
|
2061
|
-
|
|
2062
|
-
if (metadata.mcpConfigured !== undefined) {
|
|
2063
|
-
update.mcpConfigured = metadata.mcpConfigured;
|
|
2064
|
-
}
|
|
2065
|
-
if (metadata.mcpEndpoint !== undefined) {
|
|
2066
|
-
update.mcpEndpoint = metadata.mcpEndpoint ?? null;
|
|
2067
|
-
}
|
|
2068
|
-
if (metadata.mcpRegisteredAt !== undefined) {
|
|
2069
|
-
update.mcpRegisteredAt = metadata.mcpRegisteredAt ?? null;
|
|
2070
|
-
}
|
|
2071
|
-
return this.updateProvider(id, update);
|
|
334
|
+
return this.providerDelegate.updateProviderMcpMetadata(id, metadata);
|
|
2072
335
|
}
|
|
2073
336
|
async getSourceProjectEnabled(projectId, sourceName) {
|
|
2074
|
-
|
|
2075
|
-
const normalizedSourceName = this.normalizeSourceNameForSourceEnablement(sourceName);
|
|
2076
|
-
const { sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2077
|
-
const { and, eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2078
|
-
const rows = await this.db
|
|
2079
|
-
.select({ enabled: sourceProjectEnabled.enabled })
|
|
2080
|
-
.from(sourceProjectEnabled)
|
|
2081
|
-
.where(and(eq(sourceProjectEnabled.projectId, normalizedProjectId), eq(sourceProjectEnabled.sourceName, normalizedSourceName)))
|
|
2082
|
-
.limit(1);
|
|
2083
|
-
return rows[0] ? Boolean(rows[0].enabled) : null;
|
|
337
|
+
return this.skillSourceDelegate.getSourceProjectEnabled(projectId, sourceName);
|
|
2084
338
|
}
|
|
2085
339
|
async setSourceProjectEnabled(projectId, sourceName, enabled) {
|
|
2086
|
-
|
|
2087
|
-
const normalizedSourceName = this.normalizeSourceNameForSourceEnablement(sourceName);
|
|
2088
|
-
const { sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2089
|
-
const { and, eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2090
|
-
const existing = await this.db
|
|
2091
|
-
.select({ id: sourceProjectEnabled.id })
|
|
2092
|
-
.from(sourceProjectEnabled)
|
|
2093
|
-
.where(and(eq(sourceProjectEnabled.projectId, normalizedProjectId), eq(sourceProjectEnabled.sourceName, normalizedSourceName)))
|
|
2094
|
-
.limit(1);
|
|
2095
|
-
if (existing[0]) {
|
|
2096
|
-
await this.db
|
|
2097
|
-
.update(sourceProjectEnabled)
|
|
2098
|
-
.set({ enabled })
|
|
2099
|
-
.where(eq(sourceProjectEnabled.id, existing[0].id));
|
|
2100
|
-
return;
|
|
2101
|
-
}
|
|
2102
|
-
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
2103
|
-
await this.db.insert(sourceProjectEnabled).values({
|
|
2104
|
-
id: randomUUID(),
|
|
2105
|
-
projectId: normalizedProjectId,
|
|
2106
|
-
sourceName: normalizedSourceName,
|
|
2107
|
-
enabled,
|
|
2108
|
-
createdAt: new Date().toISOString(),
|
|
2109
|
-
});
|
|
340
|
+
return this.skillSourceDelegate.setSourceProjectEnabled(projectId, sourceName, enabled);
|
|
2110
341
|
}
|
|
2111
342
|
async listSourceProjectEnabled(projectId) {
|
|
2112
|
-
|
|
2113
|
-
const { sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2114
|
-
const { asc, eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2115
|
-
const rows = await this.db
|
|
2116
|
-
.select({
|
|
2117
|
-
sourceName: sourceProjectEnabled.sourceName,
|
|
2118
|
-
enabled: sourceProjectEnabled.enabled,
|
|
2119
|
-
})
|
|
2120
|
-
.from(sourceProjectEnabled)
|
|
2121
|
-
.where(eq(sourceProjectEnabled.projectId, normalizedProjectId))
|
|
2122
|
-
.orderBy(asc(sourceProjectEnabled.sourceName));
|
|
2123
|
-
return rows.map((row) => ({
|
|
2124
|
-
sourceName: row.sourceName,
|
|
2125
|
-
enabled: Boolean(row.enabled),
|
|
2126
|
-
}));
|
|
343
|
+
return this.skillSourceDelegate.listSourceProjectEnabled(projectId);
|
|
2127
344
|
}
|
|
2128
345
|
async seedSourceProjectDisabled(projectId, sourceNames) {
|
|
2129
|
-
|
|
2130
|
-
const normalizedSourceNames = [
|
|
2131
|
-
...new Set(sourceNames.map((name) => name.trim().toLowerCase())),
|
|
2132
|
-
].filter((name) => name.length > 0);
|
|
2133
|
-
if (normalizedSourceNames.length === 0) {
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
2137
|
-
if (!sqlite || typeof sqlite.exec !== 'function') {
|
|
2138
|
-
throw new error_types_1.StorageError('Unable to access underlying SQLite client');
|
|
2139
|
-
}
|
|
2140
|
-
sqlite.exec('BEGIN IMMEDIATE TRANSACTION');
|
|
2141
|
-
try {
|
|
2142
|
-
const { sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2143
|
-
const { and, eq, inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2144
|
-
const existingRows = await this.db
|
|
2145
|
-
.select({ sourceName: sourceProjectEnabled.sourceName })
|
|
2146
|
-
.from(sourceProjectEnabled)
|
|
2147
|
-
.where(and(eq(sourceProjectEnabled.projectId, normalizedProjectId), inArray(sourceProjectEnabled.sourceName, normalizedSourceNames)));
|
|
2148
|
-
const existingSourceNames = new Set(existingRows.map((row) => row.sourceName));
|
|
2149
|
-
const sourceNamesToInsert = normalizedSourceNames.filter((sourceName) => !existingSourceNames.has(sourceName));
|
|
2150
|
-
if (sourceNamesToInsert.length > 0) {
|
|
2151
|
-
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
2152
|
-
const now = new Date().toISOString();
|
|
2153
|
-
await this.db.insert(sourceProjectEnabled).values(sourceNamesToInsert.map((sourceName) => ({
|
|
2154
|
-
id: randomUUID(),
|
|
2155
|
-
projectId: normalizedProjectId,
|
|
2156
|
-
sourceName,
|
|
2157
|
-
enabled: false,
|
|
2158
|
-
createdAt: now,
|
|
2159
|
-
})));
|
|
2160
|
-
}
|
|
2161
|
-
sqlite.exec('COMMIT');
|
|
2162
|
-
}
|
|
2163
|
-
catch (error) {
|
|
2164
|
-
try {
|
|
2165
|
-
sqlite.exec('ROLLBACK');
|
|
2166
|
-
}
|
|
2167
|
-
catch (rollbackError) {
|
|
2168
|
-
logger.error({ rollbackError }, 'Failed to rollback seedSourceProjectDisabled transaction');
|
|
2169
|
-
}
|
|
2170
|
-
throw error;
|
|
2171
|
-
}
|
|
346
|
+
return this.skillSourceDelegate.seedSourceProjectDisabled(projectId, sourceNames);
|
|
2172
347
|
}
|
|
2173
348
|
async deleteSourceProjectEnabledBySource(sourceName) {
|
|
2174
|
-
|
|
2175
|
-
const { sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2176
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2177
|
-
await this.db
|
|
2178
|
-
.delete(sourceProjectEnabled)
|
|
2179
|
-
.where(eq(sourceProjectEnabled.sourceName, normalizedSourceName));
|
|
349
|
+
return this.skillSourceDelegate.deleteSourceProjectEnabledBySource(sourceName);
|
|
2180
350
|
}
|
|
2181
351
|
async listCommunitySkillSources() {
|
|
2182
|
-
|
|
2183
|
-
const { asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2184
|
-
const rows = await this.db
|
|
2185
|
-
.select()
|
|
2186
|
-
.from(communitySkillSources)
|
|
2187
|
-
.orderBy(asc(communitySkillSources.name));
|
|
2188
|
-
return rows;
|
|
352
|
+
return this.skillSourceDelegate.listCommunitySkillSources();
|
|
2189
353
|
}
|
|
2190
354
|
async getCommunitySkillSource(id) {
|
|
2191
|
-
|
|
2192
|
-
if (!normalizedId) {
|
|
2193
|
-
throw new error_types_1.ValidationError('id is required.', { fieldName: 'id' });
|
|
2194
|
-
}
|
|
2195
|
-
const { communitySkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2196
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2197
|
-
const rows = await this.db
|
|
2198
|
-
.select()
|
|
2199
|
-
.from(communitySkillSources)
|
|
2200
|
-
.where(eq(communitySkillSources.id, normalizedId))
|
|
2201
|
-
.limit(1);
|
|
2202
|
-
if (!rows[0]) {
|
|
2203
|
-
throw new error_types_1.NotFoundError('Community skill source', normalizedId);
|
|
2204
|
-
}
|
|
2205
|
-
return rows[0];
|
|
355
|
+
return this.skillSourceDelegate.getCommunitySkillSource(id);
|
|
2206
356
|
}
|
|
2207
357
|
async getCommunitySkillSourceByName(name) {
|
|
2208
|
-
|
|
2209
|
-
const { communitySkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2210
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2211
|
-
const rows = await this.db
|
|
2212
|
-
.select()
|
|
2213
|
-
.from(communitySkillSources)
|
|
2214
|
-
.where(eq(communitySkillSources.name, normalizedName))
|
|
2215
|
-
.limit(1);
|
|
2216
|
-
return rows[0] ?? null;
|
|
358
|
+
return this.skillSourceDelegate.getCommunitySkillSourceByName(name);
|
|
2217
359
|
}
|
|
2218
360
|
async createCommunitySkillSource(data) {
|
|
2219
|
-
|
|
2220
|
-
const normalizedRepoOwner = this.normalizeCommunityRepoPart(data.repoOwner, 'repoOwner');
|
|
2221
|
-
const normalizedRepoName = this.normalizeCommunityRepoPart(data.repoName, 'repoName');
|
|
2222
|
-
const normalizedBranch = this.normalizeCommunityBranch(data.branch);
|
|
2223
|
-
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
2224
|
-
const now = new Date().toISOString();
|
|
2225
|
-
const record = {
|
|
2226
|
-
id: randomUUID(),
|
|
2227
|
-
name: normalizedName,
|
|
2228
|
-
repoOwner: normalizedRepoOwner,
|
|
2229
|
-
repoName: normalizedRepoName,
|
|
2230
|
-
branch: normalizedBranch,
|
|
2231
|
-
createdAt: now,
|
|
2232
|
-
updatedAt: now,
|
|
2233
|
-
};
|
|
2234
|
-
const { communitySkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2235
|
-
try {
|
|
2236
|
-
await this.db.insert(communitySkillSources).values({
|
|
2237
|
-
id: record.id,
|
|
2238
|
-
name: record.name,
|
|
2239
|
-
repoOwner: record.repoOwner,
|
|
2240
|
-
repoName: record.repoName,
|
|
2241
|
-
branch: record.branch,
|
|
2242
|
-
createdAt: record.createdAt,
|
|
2243
|
-
updatedAt: record.updatedAt,
|
|
2244
|
-
});
|
|
2245
|
-
}
|
|
2246
|
-
catch (error) {
|
|
2247
|
-
if (this.isSqliteUniqueConstraint(error)) {
|
|
2248
|
-
const rawMessage = typeof error === 'object' && error !== null && 'message' in error
|
|
2249
|
-
? String(error.message ?? '')
|
|
2250
|
-
: '';
|
|
2251
|
-
if (rawMessage.includes('community_skill_sources.name')) {
|
|
2252
|
-
throw new error_types_1.ConflictError('Community skill source name already exists.', {
|
|
2253
|
-
name: normalizedName,
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2256
|
-
throw new error_types_1.ConflictError('Community skill source repository already exists.', {
|
|
2257
|
-
repoOwner: normalizedRepoOwner,
|
|
2258
|
-
repoName: normalizedRepoName,
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
throw new error_types_1.StorageError('Failed to create community skill source.', {
|
|
2262
|
-
name: normalizedName,
|
|
2263
|
-
repoOwner: normalizedRepoOwner,
|
|
2264
|
-
repoName: normalizedRepoName,
|
|
2265
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
2266
|
-
});
|
|
2267
|
-
}
|
|
2268
|
-
logger.info({ communitySkillSourceId: record.id, name: record.name }, 'Created community skill source');
|
|
2269
|
-
return record;
|
|
361
|
+
return this.skillSourceDelegate.createCommunitySkillSource(data);
|
|
2270
362
|
}
|
|
2271
363
|
async deleteCommunitySkillSource(id) {
|
|
2272
|
-
|
|
2273
|
-
const { communitySkillSources, skills, sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2274
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2275
|
-
await this.db.transaction(async (tx) => {
|
|
2276
|
-
await tx.delete(skills).where(eq(skills.source, source.name));
|
|
2277
|
-
await tx.delete(sourceProjectEnabled).where(eq(sourceProjectEnabled.sourceName, source.name));
|
|
2278
|
-
await tx.delete(communitySkillSources).where(eq(communitySkillSources.id, source.id));
|
|
2279
|
-
});
|
|
2280
|
-
logger.info({ communitySkillSourceId: source.id, sourceName: source.name }, 'Deleted community skill source and related skills');
|
|
364
|
+
return this.skillSourceDelegate.deleteCommunitySkillSource(id);
|
|
2281
365
|
}
|
|
2282
366
|
async listLocalSkillSources() {
|
|
2283
|
-
|
|
2284
|
-
const { asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2285
|
-
const rows = await this.db
|
|
2286
|
-
.select()
|
|
2287
|
-
.from(localSkillSources)
|
|
2288
|
-
.orderBy(asc(localSkillSources.name));
|
|
2289
|
-
return rows;
|
|
367
|
+
return this.skillSourceDelegate.listLocalSkillSources();
|
|
2290
368
|
}
|
|
2291
369
|
async getLocalSkillSource(id) {
|
|
2292
|
-
|
|
2293
|
-
if (!normalizedId) {
|
|
2294
|
-
throw new error_types_1.ValidationError('id is required.', { fieldName: 'id' });
|
|
2295
|
-
}
|
|
2296
|
-
const { localSkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2297
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2298
|
-
const rows = await this.db
|
|
2299
|
-
.select()
|
|
2300
|
-
.from(localSkillSources)
|
|
2301
|
-
.where(eq(localSkillSources.id, normalizedId))
|
|
2302
|
-
.limit(1);
|
|
2303
|
-
return rows[0] ?? null;
|
|
370
|
+
return this.skillSourceDelegate.getLocalSkillSource(id);
|
|
2304
371
|
}
|
|
2305
372
|
async createLocalSkillSource(data) {
|
|
2306
|
-
|
|
2307
|
-
const normalizedFolderPath = this.normalizeLocalSkillSourceFolderPath(data.folderPath);
|
|
2308
|
-
await this.assertLocalSourceNameAvailableAcrossTypes(normalizedName);
|
|
2309
|
-
const { randomUUID } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
2310
|
-
const now = new Date().toISOString();
|
|
2311
|
-
const record = {
|
|
2312
|
-
id: randomUUID(),
|
|
2313
|
-
name: normalizedName,
|
|
2314
|
-
folderPath: normalizedFolderPath,
|
|
2315
|
-
createdAt: now,
|
|
2316
|
-
updatedAt: now,
|
|
2317
|
-
};
|
|
2318
|
-
const { localSkillSources } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2319
|
-
try {
|
|
2320
|
-
await this.db.insert(localSkillSources).values({
|
|
2321
|
-
id: record.id,
|
|
2322
|
-
name: record.name,
|
|
2323
|
-
folderPath: record.folderPath,
|
|
2324
|
-
createdAt: record.createdAt,
|
|
2325
|
-
updatedAt: record.updatedAt,
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
catch (error) {
|
|
2329
|
-
if (this.isSqliteUniqueConstraint(error)) {
|
|
2330
|
-
const rawMessage = typeof error === 'object' && error !== null && 'message' in error
|
|
2331
|
-
? String(error.message ?? '')
|
|
2332
|
-
: '';
|
|
2333
|
-
if (rawMessage.includes('local_skill_sources.name')) {
|
|
2334
|
-
throw new error_types_1.ConflictError('Local skill source name already exists.', {
|
|
2335
|
-
name: normalizedName,
|
|
2336
|
-
});
|
|
2337
|
-
}
|
|
2338
|
-
if (rawMessage.includes('local_skill_sources.folder_path')) {
|
|
2339
|
-
throw new error_types_1.ConflictError('Local skill source folder path already exists.', {
|
|
2340
|
-
folderPath: normalizedFolderPath,
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2343
|
-
throw new error_types_1.ConflictError('Local skill source already exists.', {
|
|
2344
|
-
name: normalizedName,
|
|
2345
|
-
folderPath: normalizedFolderPath,
|
|
2346
|
-
});
|
|
2347
|
-
}
|
|
2348
|
-
throw new error_types_1.StorageError('Failed to create local skill source.', {
|
|
2349
|
-
name: normalizedName,
|
|
2350
|
-
folderPath: normalizedFolderPath,
|
|
2351
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
2354
|
-
logger.info({ localSkillSourceId: record.id, name: record.name }, 'Created local skill source');
|
|
2355
|
-
return record;
|
|
373
|
+
return this.skillSourceDelegate.createLocalSkillSource(data);
|
|
2356
374
|
}
|
|
2357
375
|
async deleteLocalSkillSource(id) {
|
|
2358
|
-
|
|
2359
|
-
if (!source) {
|
|
2360
|
-
throw new error_types_1.NotFoundError('Local skill source', id.trim());
|
|
2361
|
-
}
|
|
2362
|
-
const { localSkillSources, skills, sourceProjectEnabled } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2363
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2364
|
-
await this.db.transaction(async (tx) => {
|
|
2365
|
-
await tx.delete(skills).where(eq(skills.source, source.name));
|
|
2366
|
-
await tx.delete(sourceProjectEnabled).where(eq(sourceProjectEnabled.sourceName, source.name));
|
|
2367
|
-
await tx.delete(localSkillSources).where(eq(localSkillSources.id, source.id));
|
|
2368
|
-
});
|
|
2369
|
-
logger.info({ localSkillSourceId: source.id, sourceName: source.name }, 'Deleted local skill source and related skills');
|
|
376
|
+
return this.skillSourceDelegate.deleteLocalSkillSource(id);
|
|
2370
377
|
}
|
|
2371
378
|
async createAgentProfile(data) {
|
|
2372
|
-
|
|
2373
|
-
const now = new Date().toISOString();
|
|
2374
|
-
const { agentProfiles } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2375
|
-
const profile = {
|
|
2376
|
-
id: randomUUID(),
|
|
2377
|
-
projectId: data.projectId ?? null,
|
|
2378
|
-
name: data.name,
|
|
2379
|
-
familySlug: data.familySlug ?? null,
|
|
2380
|
-
systemPrompt: data.systemPrompt ?? null,
|
|
2381
|
-
instructions: data.instructions ?? null,
|
|
2382
|
-
temperature: data.temperature ?? null,
|
|
2383
|
-
maxTokens: data.maxTokens ?? null,
|
|
2384
|
-
createdAt: now,
|
|
2385
|
-
updatedAt: now,
|
|
2386
|
-
};
|
|
2387
|
-
await this.db.insert(agentProfiles).values({
|
|
2388
|
-
id: profile.id,
|
|
2389
|
-
projectId: profile.projectId,
|
|
2390
|
-
name: profile.name,
|
|
2391
|
-
familySlug: profile.familySlug,
|
|
2392
|
-
systemPrompt: profile.systemPrompt,
|
|
2393
|
-
instructions: profile.instructions,
|
|
2394
|
-
temperature: profile.temperature != null ? Math.round(profile.temperature * 100) : null,
|
|
2395
|
-
maxTokens: profile.maxTokens,
|
|
2396
|
-
createdAt: profile.createdAt,
|
|
2397
|
-
updatedAt: profile.updatedAt,
|
|
2398
|
-
});
|
|
2399
|
-
return profile;
|
|
379
|
+
return this.agentProfileDelegate.createAgentProfile(data);
|
|
2400
380
|
}
|
|
2401
381
|
async getAgentProfile(id) {
|
|
2402
|
-
|
|
2403
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2404
|
-
const result = await this.db
|
|
2405
|
-
.select()
|
|
2406
|
-
.from(agentProfiles)
|
|
2407
|
-
.where(eq(agentProfiles.id, id))
|
|
2408
|
-
.limit(1);
|
|
2409
|
-
if (!result[0]) {
|
|
2410
|
-
throw new error_types_1.NotFoundError('Agent profile', id);
|
|
2411
|
-
}
|
|
2412
|
-
const profile = result[0];
|
|
2413
|
-
return {
|
|
2414
|
-
...profile,
|
|
2415
|
-
temperature: profile.temperature != null ? profile.temperature / 100 : null,
|
|
2416
|
-
};
|
|
382
|
+
return this.agentProfileDelegate.getAgentProfile(id);
|
|
2417
383
|
}
|
|
2418
384
|
async listAgentProfiles(options = {}) {
|
|
2419
|
-
|
|
2420
|
-
const { eq, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2421
|
-
const limit = options.limit || 100;
|
|
2422
|
-
const offset = options.offset || 0;
|
|
2423
|
-
let whereClause;
|
|
2424
|
-
if (options.projectId !== undefined) {
|
|
2425
|
-
whereClause =
|
|
2426
|
-
options.projectId === null
|
|
2427
|
-
? isNull(agentProfiles.projectId)
|
|
2428
|
-
: eq(agentProfiles.projectId, options.projectId);
|
|
2429
|
-
}
|
|
2430
|
-
const items = await this.db
|
|
2431
|
-
.select()
|
|
2432
|
-
.from(agentProfiles)
|
|
2433
|
-
.where(whereClause)
|
|
2434
|
-
.limit(limit)
|
|
2435
|
-
.offset(offset);
|
|
2436
|
-
return {
|
|
2437
|
-
items: items.map((p) => ({
|
|
2438
|
-
...p,
|
|
2439
|
-
temperature: p.temperature != null ? p.temperature / 100 : null,
|
|
2440
|
-
})),
|
|
2441
|
-
total: items.length,
|
|
2442
|
-
limit,
|
|
2443
|
-
offset,
|
|
2444
|
-
};
|
|
385
|
+
return this.agentProfileDelegate.listAgentProfiles(options);
|
|
2445
386
|
}
|
|
2446
387
|
async updateAgentProfile(id, data) {
|
|
2447
|
-
|
|
2448
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2449
|
-
const now = new Date().toISOString();
|
|
2450
|
-
const updateData = { ...data };
|
|
2451
|
-
if (data.temperature !== undefined && data.temperature !== null) {
|
|
2452
|
-
updateData.temperature = Math.round(data.temperature * 100);
|
|
2453
|
-
}
|
|
2454
|
-
if (data.temperature === null) {
|
|
2455
|
-
updateData.temperature = null;
|
|
2456
|
-
}
|
|
2457
|
-
if (data.instructions !== undefined) {
|
|
2458
|
-
updateData.instructions = data.instructions ?? null;
|
|
2459
|
-
}
|
|
2460
|
-
if (data.familySlug !== undefined) {
|
|
2461
|
-
updateData.familySlug = data.familySlug ?? null;
|
|
2462
|
-
}
|
|
2463
|
-
await this.db
|
|
2464
|
-
.update(agentProfiles)
|
|
2465
|
-
.set({ ...updateData, updatedAt: now })
|
|
2466
|
-
.where(eq(agentProfiles.id, id));
|
|
2467
|
-
return this.getAgentProfile(id);
|
|
388
|
+
return this.agentProfileDelegate.updateAgentProfile(id, data);
|
|
2468
389
|
}
|
|
2469
390
|
async deleteAgentProfile(id) {
|
|
2470
|
-
|
|
2471
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2472
|
-
await this.db.delete(agentProfiles).where(eq(agentProfiles.id, id));
|
|
391
|
+
return this.agentProfileDelegate.deleteAgentProfile(id);
|
|
2473
392
|
}
|
|
2474
393
|
async setAgentProfilePrompts(profileId, promptIdsOrdered) {
|
|
2475
|
-
|
|
2476
|
-
const { eq, inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2477
|
-
const profile = await this.getAgentProfile(profileId);
|
|
2478
|
-
if (promptIdsOrdered.length > 0) {
|
|
2479
|
-
const items = await this.db
|
|
2480
|
-
.select({ id: prompts.id, projectId: prompts.projectId })
|
|
2481
|
-
.from(prompts)
|
|
2482
|
-
.where(inArray(prompts.id, promptIdsOrdered));
|
|
2483
|
-
const foundIds = new Set(items.map((i) => i.id));
|
|
2484
|
-
const missing = promptIdsOrdered.filter((id) => !foundIds.has(id));
|
|
2485
|
-
if (missing.length > 0) {
|
|
2486
|
-
throw new error_types_1.ValidationError('Unknown prompt ids provided', { missing });
|
|
2487
|
-
}
|
|
2488
|
-
const crossProject = items.filter((i) => i.projectId !== (profile.projectId ?? null));
|
|
2489
|
-
if (crossProject.length > 0) {
|
|
2490
|
-
throw new error_types_1.ValidationError('Cross-project prompts are not allowed for this profile', {
|
|
2491
|
-
profileProjectId: profile.projectId ?? null,
|
|
2492
|
-
promptIds: crossProject.map((i) => i.id),
|
|
2493
|
-
});
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
await this.db.transaction(async (tx) => {
|
|
2497
|
-
await tx.delete(agentProfilePrompts).where(eq(agentProfilePrompts.profileId, profileId));
|
|
2498
|
-
if (promptIdsOrdered.length === 0)
|
|
2499
|
-
return;
|
|
2500
|
-
const base = new Date();
|
|
2501
|
-
const rows = promptIdsOrdered.map((pid, idx) => ({
|
|
2502
|
-
profileId,
|
|
2503
|
-
promptId: pid,
|
|
2504
|
-
createdAt: new Date(base.getTime() + idx).toISOString(),
|
|
2505
|
-
}));
|
|
2506
|
-
await tx.insert(agentProfilePrompts).values(rows);
|
|
2507
|
-
});
|
|
394
|
+
return this.agentProfileDelegate.setAgentProfilePrompts(profileId, promptIdsOrdered);
|
|
2508
395
|
}
|
|
2509
396
|
async getAgentProfilePrompts(profileId) {
|
|
2510
|
-
|
|
2511
|
-
const { eq, asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2512
|
-
const rows = await this.db
|
|
2513
|
-
.select({ promptId: agentProfilePrompts.promptId, createdAt: agentProfilePrompts.createdAt })
|
|
2514
|
-
.from(agentProfilePrompts)
|
|
2515
|
-
.where(eq(agentProfilePrompts.profileId, profileId))
|
|
2516
|
-
.orderBy(asc(agentProfilePrompts.createdAt));
|
|
2517
|
-
return rows;
|
|
397
|
+
return this.agentProfileDelegate.getAgentProfilePrompts(profileId);
|
|
2518
398
|
}
|
|
2519
399
|
async getAgentProfileWithPrompts(id) {
|
|
2520
|
-
|
|
2521
|
-
const { agentProfilePrompts, prompts } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2522
|
-
const { eq, asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2523
|
-
const rows = await this.db
|
|
2524
|
-
.select({
|
|
2525
|
-
promptId: agentProfilePrompts.promptId,
|
|
2526
|
-
createdAt: agentProfilePrompts.createdAt,
|
|
2527
|
-
title: prompts.title,
|
|
2528
|
-
})
|
|
2529
|
-
.from(agentProfilePrompts)
|
|
2530
|
-
.innerJoin(prompts, eq(agentProfilePrompts.promptId, prompts.id))
|
|
2531
|
-
.where(eq(agentProfilePrompts.profileId, id))
|
|
2532
|
-
.orderBy(asc(agentProfilePrompts.createdAt));
|
|
2533
|
-
const promptsDetailed = rows.map((row, idx) => ({
|
|
2534
|
-
promptId: row.promptId,
|
|
2535
|
-
title: row.title,
|
|
2536
|
-
order: idx + 1,
|
|
2537
|
-
}));
|
|
2538
|
-
return { ...profile, prompts: promptsDetailed };
|
|
400
|
+
return this.agentProfileDelegate.getAgentProfileWithPrompts(id);
|
|
2539
401
|
}
|
|
2540
402
|
async listAgentProfilesWithPrompts(options = {}) {
|
|
2541
|
-
|
|
2542
|
-
if (!base.items.length)
|
|
2543
|
-
return { ...base, items: [] };
|
|
2544
|
-
const ids = base.items.map((p) => p.id);
|
|
2545
|
-
const { agentProfilePrompts, prompts, profileProviderConfigs, providers } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2546
|
-
const { inArray, asc, eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2547
|
-
const promptRows = await this.db
|
|
2548
|
-
.select({
|
|
2549
|
-
profileId: agentProfilePrompts.profileId,
|
|
2550
|
-
promptId: agentProfilePrompts.promptId,
|
|
2551
|
-
createdAt: agentProfilePrompts.createdAt,
|
|
2552
|
-
title: prompts.title,
|
|
2553
|
-
})
|
|
2554
|
-
.from(agentProfilePrompts)
|
|
2555
|
-
.innerJoin(prompts, eq(agentProfilePrompts.promptId, prompts.id))
|
|
2556
|
-
.where(inArray(agentProfilePrompts.profileId, ids))
|
|
2557
|
-
.orderBy(asc(agentProfilePrompts.profileId), asc(agentProfilePrompts.createdAt));
|
|
2558
|
-
const groupedPrompts = new Map();
|
|
2559
|
-
for (const r of promptRows) {
|
|
2560
|
-
const pid = r.profileId;
|
|
2561
|
-
const arr = groupedPrompts.get(pid) ?? [];
|
|
2562
|
-
arr.push({
|
|
2563
|
-
promptId: r.promptId,
|
|
2564
|
-
title: r.title,
|
|
2565
|
-
createdAt: r.createdAt,
|
|
2566
|
-
});
|
|
2567
|
-
groupedPrompts.set(pid, arr);
|
|
2568
|
-
}
|
|
2569
|
-
const configRows = await this.db
|
|
2570
|
-
.select({
|
|
2571
|
-
profileId: profileProviderConfigs.profileId,
|
|
2572
|
-
providerId: profileProviderConfigs.providerId,
|
|
2573
|
-
createdAt: profileProviderConfigs.createdAt,
|
|
2574
|
-
})
|
|
2575
|
-
.from(profileProviderConfigs)
|
|
2576
|
-
.where(inArray(profileProviderConfigs.profileId, ids))
|
|
2577
|
-
.orderBy(asc(profileProviderConfigs.profileId), asc(profileProviderConfigs.createdAt));
|
|
2578
|
-
const providerIds = [...new Set(configRows.map((r) => r.providerId))];
|
|
2579
|
-
const providerMap = new Map();
|
|
2580
|
-
if (providerIds.length > 0) {
|
|
2581
|
-
const providerRows = await this.db
|
|
2582
|
-
.select({ id: providers.id, name: providers.name })
|
|
2583
|
-
.from(providers)
|
|
2584
|
-
.where(inArray(providers.id, providerIds));
|
|
2585
|
-
for (const p of providerRows) {
|
|
2586
|
-
providerMap.set(p.id, { id: p.id, name: p.name });
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
const firstProviderByProfile = new Map();
|
|
2590
|
-
for (const r of configRows) {
|
|
2591
|
-
const pid = r.profileId;
|
|
2592
|
-
if (!firstProviderByProfile.has(pid)) {
|
|
2593
|
-
const provider = providerMap.get(r.providerId);
|
|
2594
|
-
if (provider) {
|
|
2595
|
-
firstProviderByProfile.set(pid, provider);
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
const items = base.items.map((p) => {
|
|
2600
|
-
const arr = groupedPrompts.get(p.id) ?? [];
|
|
2601
|
-
const promptsDetailed = arr.map((row, idx) => ({
|
|
2602
|
-
promptId: row.promptId,
|
|
2603
|
-
title: row.title,
|
|
2604
|
-
order: idx + 1,
|
|
2605
|
-
}));
|
|
2606
|
-
const provider = firstProviderByProfile.get(p.id);
|
|
2607
|
-
return { ...p, prompts: promptsDetailed, ...(provider && { provider }) };
|
|
2608
|
-
});
|
|
2609
|
-
return { ...base, items };
|
|
403
|
+
return this.agentProfileDelegate.listAgentProfilesWithPrompts(options);
|
|
2610
404
|
}
|
|
2611
405
|
async createProfileProviderConfig(data) {
|
|
2612
|
-
|
|
2613
|
-
const now = new Date().toISOString();
|
|
2614
|
-
const { profileProviderConfigs } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2615
|
-
const { eq, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2616
|
-
let position = data.position;
|
|
2617
|
-
if (position === undefined) {
|
|
2618
|
-
const maxResult = await this.db
|
|
2619
|
-
.select({ maxPos: sql `COALESCE(MAX(${profileProviderConfigs.position}), -1)` })
|
|
2620
|
-
.from(profileProviderConfigs)
|
|
2621
|
-
.where(eq(profileProviderConfigs.profileId, data.profileId));
|
|
2622
|
-
position = (maxResult[0]?.maxPos ?? -1) + 1;
|
|
2623
|
-
}
|
|
2624
|
-
const config = {
|
|
2625
|
-
id: randomUUID(),
|
|
2626
|
-
profileId: data.profileId,
|
|
2627
|
-
providerId: data.providerId,
|
|
2628
|
-
name: data.name,
|
|
2629
|
-
options: data.options ?? null,
|
|
2630
|
-
env: data.env ?? null,
|
|
2631
|
-
position,
|
|
2632
|
-
createdAt: now,
|
|
2633
|
-
updatedAt: now,
|
|
2634
|
-
};
|
|
2635
|
-
await this.db.insert(profileProviderConfigs).values({
|
|
2636
|
-
id: config.id,
|
|
2637
|
-
profileId: config.profileId,
|
|
2638
|
-
providerId: config.providerId,
|
|
2639
|
-
name: config.name,
|
|
2640
|
-
options: config.options,
|
|
2641
|
-
env: config.env ? JSON.stringify(config.env) : null,
|
|
2642
|
-
position: config.position,
|
|
2643
|
-
createdAt: config.createdAt,
|
|
2644
|
-
updatedAt: config.updatedAt,
|
|
2645
|
-
});
|
|
2646
|
-
logger.info({ configId: config.id, profileId: config.profileId, position }, 'Created profile provider config');
|
|
2647
|
-
return config;
|
|
406
|
+
return this.profileProviderConfigDelegate.createProfileProviderConfig(data);
|
|
2648
407
|
}
|
|
2649
408
|
async getProfileProviderConfig(id) {
|
|
2650
|
-
|
|
2651
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2652
|
-
const result = await this.db
|
|
2653
|
-
.select()
|
|
2654
|
-
.from(profileProviderConfigs)
|
|
2655
|
-
.where(eq(profileProviderConfigs.id, id))
|
|
2656
|
-
.limit(1);
|
|
2657
|
-
if (!result[0]) {
|
|
2658
|
-
throw new error_types_1.NotFoundError('ProfileProviderConfig', id);
|
|
2659
|
-
}
|
|
2660
|
-
const row = result[0];
|
|
2661
|
-
return {
|
|
2662
|
-
id: row.id,
|
|
2663
|
-
profileId: row.profileId,
|
|
2664
|
-
providerId: row.providerId,
|
|
2665
|
-
name: row.name,
|
|
2666
|
-
options: row.options,
|
|
2667
|
-
env: this.parseProviderConfigEnv(row.env, row.id, row.profileId),
|
|
2668
|
-
position: row.position,
|
|
2669
|
-
createdAt: row.createdAt,
|
|
2670
|
-
updatedAt: row.updatedAt,
|
|
2671
|
-
};
|
|
409
|
+
return this.profileProviderConfigDelegate.getProfileProviderConfig(id);
|
|
2672
410
|
}
|
|
2673
411
|
async listProfileProviderConfigsByProfile(profileId) {
|
|
2674
|
-
|
|
2675
|
-
const { eq, asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2676
|
-
const results = await this.db
|
|
2677
|
-
.select()
|
|
2678
|
-
.from(profileProviderConfigs)
|
|
2679
|
-
.where(eq(profileProviderConfigs.profileId, profileId))
|
|
2680
|
-
.orderBy(asc(profileProviderConfigs.position), asc(profileProviderConfigs.id));
|
|
2681
|
-
return results.map((row) => ({
|
|
2682
|
-
id: row.id,
|
|
2683
|
-
profileId: row.profileId,
|
|
2684
|
-
providerId: row.providerId,
|
|
2685
|
-
name: row.name,
|
|
2686
|
-
options: row.options,
|
|
2687
|
-
env: this.parseProviderConfigEnv(row.env, row.id, row.profileId),
|
|
2688
|
-
position: row.position,
|
|
2689
|
-
createdAt: row.createdAt,
|
|
2690
|
-
updatedAt: row.updatedAt,
|
|
2691
|
-
}));
|
|
412
|
+
return this.profileProviderConfigDelegate.listProfileProviderConfigsByProfile(profileId);
|
|
2692
413
|
}
|
|
2693
414
|
async listProfileProviderConfigsByIds(ids) {
|
|
2694
|
-
|
|
2695
|
-
return [];
|
|
2696
|
-
}
|
|
2697
|
-
const { profileProviderConfigs } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2698
|
-
const { inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2699
|
-
const results = await this.db
|
|
2700
|
-
.select()
|
|
2701
|
-
.from(profileProviderConfigs)
|
|
2702
|
-
.where(inArray(profileProviderConfigs.id, ids));
|
|
2703
|
-
return results.map((row) => ({
|
|
2704
|
-
id: row.id,
|
|
2705
|
-
profileId: row.profileId,
|
|
2706
|
-
providerId: row.providerId,
|
|
2707
|
-
name: row.name,
|
|
2708
|
-
options: row.options,
|
|
2709
|
-
env: this.parseProviderConfigEnv(row.env, row.id, row.profileId),
|
|
2710
|
-
position: row.position,
|
|
2711
|
-
createdAt: row.createdAt,
|
|
2712
|
-
updatedAt: row.updatedAt,
|
|
2713
|
-
}));
|
|
415
|
+
return this.profileProviderConfigDelegate.listProfileProviderConfigsByIds(ids);
|
|
2714
416
|
}
|
|
2715
417
|
async listAllProfileProviderConfigs() {
|
|
2716
|
-
|
|
2717
|
-
const results = await this.db.select().from(profileProviderConfigs);
|
|
2718
|
-
return results.map((row) => ({
|
|
2719
|
-
id: row.id,
|
|
2720
|
-
profileId: row.profileId,
|
|
2721
|
-
providerId: row.providerId,
|
|
2722
|
-
name: row.name,
|
|
2723
|
-
options: row.options,
|
|
2724
|
-
env: this.parseProviderConfigEnv(row.env, row.id, row.profileId),
|
|
2725
|
-
position: row.position,
|
|
2726
|
-
createdAt: row.createdAt,
|
|
2727
|
-
updatedAt: row.updatedAt,
|
|
2728
|
-
}));
|
|
418
|
+
return this.profileProviderConfigDelegate.listAllProfileProviderConfigs();
|
|
2729
419
|
}
|
|
2730
420
|
async updateProfileProviderConfig(id, data) {
|
|
2731
|
-
|
|
2732
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2733
|
-
const now = new Date().toISOString();
|
|
2734
|
-
const updateData = { updatedAt: now };
|
|
2735
|
-
if (data.providerId !== undefined) {
|
|
2736
|
-
updateData.providerId = data.providerId;
|
|
2737
|
-
}
|
|
2738
|
-
if (data.name !== undefined) {
|
|
2739
|
-
updateData.name = data.name;
|
|
2740
|
-
}
|
|
2741
|
-
if (data.options !== undefined) {
|
|
2742
|
-
updateData.options = data.options;
|
|
2743
|
-
}
|
|
2744
|
-
if (data.env !== undefined) {
|
|
2745
|
-
updateData.env = data.env ? JSON.stringify(data.env) : null;
|
|
2746
|
-
}
|
|
2747
|
-
if (data.position !== undefined) {
|
|
2748
|
-
updateData.position = data.position;
|
|
2749
|
-
}
|
|
2750
|
-
await this.db
|
|
2751
|
-
.update(profileProviderConfigs)
|
|
2752
|
-
.set(updateData)
|
|
2753
|
-
.where(eq(profileProviderConfigs.id, id));
|
|
2754
|
-
logger.info({ configId: id }, 'Updated profile provider config');
|
|
2755
|
-
return this.getProfileProviderConfig(id);
|
|
421
|
+
return this.profileProviderConfigDelegate.updateProfileProviderConfig(id, data);
|
|
2756
422
|
}
|
|
2757
423
|
async deleteProfileProviderConfig(id) {
|
|
2758
|
-
|
|
2759
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2760
|
-
const { agents } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2761
|
-
const agentRefs = await this.db
|
|
2762
|
-
.select({ id: agents.id })
|
|
2763
|
-
.from(agents)
|
|
2764
|
-
.where(eq(agents.providerConfigId, id))
|
|
2765
|
-
.limit(1);
|
|
2766
|
-
if (agentRefs.length > 0) {
|
|
2767
|
-
throw new error_types_1.ValidationError('Cannot delete provider config: still referenced by agents', {
|
|
2768
|
-
configId: id,
|
|
2769
|
-
referencingAgentId: agentRefs[0].id,
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
|
-
await this.db.delete(profileProviderConfigs).where(eq(profileProviderConfigs.id, id));
|
|
2773
|
-
logger.info({ configId: id }, 'Deleted profile provider config');
|
|
424
|
+
return this.profileProviderConfigDelegate.deleteProfileProviderConfig(id);
|
|
2774
425
|
}
|
|
2775
426
|
async reorderProfileProviderConfigs(profileId, configIds) {
|
|
2776
|
-
|
|
2777
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2778
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
2779
|
-
if (!sqlite || typeof sqlite.exec !== 'function') {
|
|
2780
|
-
throw new error_types_1.StorageError('Unable to access underlying SQLite client for transaction control');
|
|
2781
|
-
}
|
|
2782
|
-
try {
|
|
2783
|
-
sqlite.exec('BEGIN IMMEDIATE TRANSACTION');
|
|
2784
|
-
const configs = await this.db
|
|
2785
|
-
.select({ position: profileProviderConfigs.position })
|
|
2786
|
-
.from(profileProviderConfigs)
|
|
2787
|
-
.where(eq(profileProviderConfigs.profileId, profileId));
|
|
2788
|
-
const maxPosition = Math.max(0, ...configs.map((c) => c.position ?? 0));
|
|
2789
|
-
const tempBase = maxPosition + 1000;
|
|
2790
|
-
for (let i = 0; i < configIds.length; i++) {
|
|
2791
|
-
await this.db
|
|
2792
|
-
.update(profileProviderConfigs)
|
|
2793
|
-
.set({ position: tempBase + i })
|
|
2794
|
-
.where(eq(profileProviderConfigs.id, configIds[i]));
|
|
2795
|
-
}
|
|
2796
|
-
for (let i = 0; i < configIds.length; i++) {
|
|
2797
|
-
await this.db
|
|
2798
|
-
.update(profileProviderConfigs)
|
|
2799
|
-
.set({ position: i })
|
|
2800
|
-
.where(eq(profileProviderConfigs.id, configIds[i]));
|
|
2801
|
-
}
|
|
2802
|
-
sqlite.exec('COMMIT');
|
|
2803
|
-
logger.info({ profileId, configIds }, 'Reordered provider configs');
|
|
2804
|
-
}
|
|
2805
|
-
catch (error) {
|
|
2806
|
-
sqlite.exec('ROLLBACK');
|
|
2807
|
-
logger.error({ error, profileId, configIds }, 'Failed to reorder provider configs, rolled back');
|
|
2808
|
-
throw error;
|
|
2809
|
-
}
|
|
427
|
+
return this.profileProviderConfigDelegate.reorderProfileProviderConfigs(profileId, configIds);
|
|
2810
428
|
}
|
|
2811
429
|
async createAgent(data) {
|
|
2812
|
-
|
|
2813
|
-
const now = new Date().toISOString();
|
|
2814
|
-
const { agents } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2815
|
-
const agent = {
|
|
2816
|
-
id: randomUUID(),
|
|
2817
|
-
...data,
|
|
2818
|
-
description: data.description ?? null,
|
|
2819
|
-
providerConfigId: data.providerConfigId,
|
|
2820
|
-
createdAt: now,
|
|
2821
|
-
updatedAt: now,
|
|
2822
|
-
};
|
|
2823
|
-
const profile = await this.getAgentProfile(agent.profileId);
|
|
2824
|
-
if (profile.projectId !== agent.projectId) {
|
|
2825
|
-
throw new error_types_1.ValidationError('Agent.profileId must belong to the same project as the agent.', {
|
|
2826
|
-
agentProjectId: agent.projectId,
|
|
2827
|
-
profileProjectId: profile.projectId,
|
|
2828
|
-
profileId: agent.profileId,
|
|
2829
|
-
});
|
|
2830
|
-
}
|
|
2831
|
-
const config = await this.getProfileProviderConfig(agent.providerConfigId);
|
|
2832
|
-
if (config.profileId !== agent.profileId) {
|
|
2833
|
-
throw new error_types_1.ValidationError('Provider config does not belong to the specified profile.', {
|
|
2834
|
-
providerConfigId: agent.providerConfigId,
|
|
2835
|
-
configProfileId: config.profileId,
|
|
2836
|
-
expectedProfileId: agent.profileId,
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
|
-
await this.db.insert(agents).values({
|
|
2840
|
-
id: agent.id,
|
|
2841
|
-
projectId: agent.projectId,
|
|
2842
|
-
profileId: agent.profileId,
|
|
2843
|
-
providerConfigId: agent.providerConfigId,
|
|
2844
|
-
name: agent.name,
|
|
2845
|
-
description: agent.description,
|
|
2846
|
-
createdAt: agent.createdAt,
|
|
2847
|
-
updatedAt: agent.updatedAt,
|
|
2848
|
-
});
|
|
2849
|
-
logger.info({ agentId: agent.id, projectId: agent.projectId }, 'Created agent');
|
|
2850
|
-
return agent;
|
|
430
|
+
return this.agentDelegate.createAgent(data);
|
|
2851
431
|
}
|
|
2852
432
|
async getAgent(id) {
|
|
2853
|
-
|
|
2854
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2855
|
-
const result = await this.db.select().from(agents).where(eq(agents.id, id)).limit(1);
|
|
2856
|
-
if (!result[0]) {
|
|
2857
|
-
throw new error_types_1.NotFoundError('Agent', id);
|
|
2858
|
-
}
|
|
2859
|
-
return result[0];
|
|
433
|
+
return this.agentDelegate.getAgent(id);
|
|
2860
434
|
}
|
|
2861
435
|
async listAgents(projectId, options = {}) {
|
|
2862
|
-
|
|
2863
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2864
|
-
const limit = options.limit || 100;
|
|
2865
|
-
const offset = options.offset || 0;
|
|
2866
|
-
const items = await this.db
|
|
2867
|
-
.select()
|
|
2868
|
-
.from(agents)
|
|
2869
|
-
.where(eq(agents.projectId, projectId))
|
|
2870
|
-
.limit(limit)
|
|
2871
|
-
.offset(offset);
|
|
2872
|
-
return {
|
|
2873
|
-
items: items,
|
|
2874
|
-
total: items.length,
|
|
2875
|
-
limit,
|
|
2876
|
-
offset,
|
|
2877
|
-
};
|
|
436
|
+
return this.agentDelegate.listAgents(projectId, options);
|
|
2878
437
|
}
|
|
2879
438
|
async getAgentByName(projectId, name) {
|
|
2880
|
-
|
|
2881
|
-
const { and, eq, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2882
|
-
const normalized = name.toLowerCase();
|
|
2883
|
-
const result = await this.db
|
|
2884
|
-
.select()
|
|
2885
|
-
.from(agents)
|
|
2886
|
-
.where(and(eq(agents.projectId, projectId), sql `lower(${agents.name}) = ${normalized}`))
|
|
2887
|
-
.limit(1);
|
|
2888
|
-
const record = result[0];
|
|
2889
|
-
if (!record) {
|
|
2890
|
-
throw new error_types_1.NotFoundError('Agent', `${projectId}:${name}`);
|
|
2891
|
-
}
|
|
2892
|
-
const agent = record;
|
|
2893
|
-
const profile = await this.getAgentProfile(agent.profileId);
|
|
2894
|
-
return { ...agent, profile };
|
|
439
|
+
return this.agentDelegate.getAgentByName(projectId, name);
|
|
2895
440
|
}
|
|
2896
441
|
async updateAgent(id, data) {
|
|
2897
|
-
|
|
2898
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2899
|
-
const now = new Date().toISOString();
|
|
2900
|
-
if (data.projectId !== undefined ||
|
|
2901
|
-
data.profileId !== undefined ||
|
|
2902
|
-
data.providerConfigId !== undefined) {
|
|
2903
|
-
const current = await this.getAgent(id);
|
|
2904
|
-
const newProjectId = data.projectId ?? current.projectId;
|
|
2905
|
-
const newProfileId = data.profileId ?? current.profileId;
|
|
2906
|
-
const newProviderConfigId = data.providerConfigId ?? current.providerConfigId;
|
|
2907
|
-
const profile = await this.getAgentProfile(newProfileId);
|
|
2908
|
-
if (profile.projectId !== newProjectId) {
|
|
2909
|
-
throw new error_types_1.ValidationError('Agent.profileId must belong to the same project as the agent.', {
|
|
2910
|
-
agentProjectId: newProjectId,
|
|
2911
|
-
profileProjectId: profile.projectId,
|
|
2912
|
-
profileId: newProfileId,
|
|
2913
|
-
});
|
|
2914
|
-
}
|
|
2915
|
-
const config = await this.getProfileProviderConfig(newProviderConfigId);
|
|
2916
|
-
if (config.profileId !== newProfileId) {
|
|
2917
|
-
throw new error_types_1.ValidationError('Provider config does not belong to the specified profile.', {
|
|
2918
|
-
providerConfigId: newProviderConfigId,
|
|
2919
|
-
configProfileId: config.profileId,
|
|
2920
|
-
expectedProfileId: newProfileId,
|
|
2921
|
-
});
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
await this.db
|
|
2925
|
-
.update(agents)
|
|
2926
|
-
.set({ ...data, updatedAt: now })
|
|
2927
|
-
.where(eq(agents.id, id));
|
|
2928
|
-
return this.getAgent(id);
|
|
442
|
+
return this.agentDelegate.updateAgent(id, data);
|
|
2929
443
|
}
|
|
2930
444
|
async deleteAgent(id) {
|
|
2931
|
-
|
|
2932
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2933
|
-
const relatedSessions = await this.db.select().from(sessions).where(eq(sessions.agentId, id));
|
|
2934
|
-
const runningSessions = relatedSessions.filter((s) => s.status === 'running');
|
|
2935
|
-
if (runningSessions.length > 0) {
|
|
2936
|
-
throw new error_types_1.ConflictError(`Cannot delete agent: ${runningSessions.length} active session(s) are still running. Please terminate the active sessions first.`);
|
|
2937
|
-
}
|
|
2938
|
-
const completedSessions = relatedSessions.filter((s) => s.status === 'stopped' || s.status === 'failed');
|
|
2939
|
-
if (completedSessions.length > 0) {
|
|
2940
|
-
logger.info({ agentId: id, count: completedSessions.length }, 'Auto-deleting completed sessions for agent');
|
|
2941
|
-
for (const session of completedSessions) {
|
|
2942
|
-
await this.db.delete(sessions).where(eq(sessions.id, session.id));
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
await this.db.delete(agents).where(eq(agents.id, id));
|
|
2946
|
-
logger.info({ agentId: id, deletedSessions: completedSessions.length }, 'Deleted agent');
|
|
445
|
+
return this.agentDelegate.deleteAgent(id);
|
|
2947
446
|
}
|
|
2948
447
|
async createRecord(data) {
|
|
2949
|
-
|
|
2950
|
-
const now = new Date().toISOString();
|
|
2951
|
-
const { records, recordTags, tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2952
|
-
const record = {
|
|
2953
|
-
id: randomUUID(),
|
|
2954
|
-
...data,
|
|
2955
|
-
version: 1,
|
|
2956
|
-
createdAt: now,
|
|
2957
|
-
updatedAt: now,
|
|
2958
|
-
};
|
|
2959
|
-
await this.db.insert(records).values({
|
|
2960
|
-
id: record.id,
|
|
2961
|
-
epicId: record.epicId,
|
|
2962
|
-
type: record.type,
|
|
2963
|
-
data: JSON.stringify(record.data),
|
|
2964
|
-
version: record.version,
|
|
2965
|
-
createdAt: record.createdAt,
|
|
2966
|
-
updatedAt: record.updatedAt,
|
|
2967
|
-
});
|
|
2968
|
-
if (data.tags?.length) {
|
|
2969
|
-
for (const tagName of data.tags) {
|
|
2970
|
-
const { eq, and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2971
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
2972
|
-
const epic = await this.db.select().from(epics).where(eq(epics.id, data.epicId)).limit(1);
|
|
2973
|
-
const projectId = epic[0]?.projectId || null;
|
|
2974
|
-
let tag = await this.db
|
|
2975
|
-
.select()
|
|
2976
|
-
.from(tags)
|
|
2977
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, projectId || ''), isNull(tags.projectId))))
|
|
2978
|
-
.limit(1);
|
|
2979
|
-
if (!tag[0]) {
|
|
2980
|
-
const newTag = await this.createTag({ projectId, name: tagName });
|
|
2981
|
-
tag = [newTag];
|
|
2982
|
-
}
|
|
2983
|
-
await this.db.insert(recordTags).values({
|
|
2984
|
-
recordId: record.id,
|
|
2985
|
-
tagId: tag[0].id,
|
|
2986
|
-
createdAt: now,
|
|
2987
|
-
});
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
logger.info({ recordId: record.id, epicId: record.epicId, type: record.type }, 'Created record');
|
|
2991
|
-
return record;
|
|
448
|
+
return this.recordDelegate.createRecord(data);
|
|
2992
449
|
}
|
|
2993
450
|
async getRecord(id) {
|
|
2994
|
-
|
|
2995
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
2996
|
-
const result = await this.db.select().from(records).where(eq(records.id, id)).limit(1);
|
|
2997
|
-
if (!result[0]) {
|
|
2998
|
-
throw new error_types_1.NotFoundError('Record', id);
|
|
2999
|
-
}
|
|
3000
|
-
const recordTagsResult = await this.db
|
|
3001
|
-
.select({ tag: tags })
|
|
3002
|
-
.from(recordTags)
|
|
3003
|
-
.innerJoin(tags, eq(recordTags.tagId, tags.id))
|
|
3004
|
-
.where(eq(recordTags.recordId, id));
|
|
3005
|
-
return {
|
|
3006
|
-
...result[0],
|
|
3007
|
-
data: result[0].data,
|
|
3008
|
-
tags: recordTagsResult.map((rt) => rt.tag.name),
|
|
3009
|
-
};
|
|
451
|
+
return this.recordDelegate.getRecord(id);
|
|
3010
452
|
}
|
|
3011
453
|
async listRecords(epicId, options = {}) {
|
|
3012
|
-
|
|
3013
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3014
|
-
const limit = options.limit || 100;
|
|
3015
|
-
const offset = options.offset || 0;
|
|
3016
|
-
const items = await this.db
|
|
3017
|
-
.select()
|
|
3018
|
-
.from(records)
|
|
3019
|
-
.where(eq(records.epicId, epicId))
|
|
3020
|
-
.limit(limit)
|
|
3021
|
-
.offset(offset);
|
|
3022
|
-
const itemsWithTags = await Promise.all(items.map((item) => this.getRecord(item.id)));
|
|
3023
|
-
return {
|
|
3024
|
-
items: itemsWithTags,
|
|
3025
|
-
total: items.length,
|
|
3026
|
-
limit,
|
|
3027
|
-
offset,
|
|
3028
|
-
};
|
|
454
|
+
return this.recordDelegate.listRecords(epicId, options);
|
|
3029
455
|
}
|
|
3030
456
|
async updateRecord(id, data, expectedVersion) {
|
|
3031
|
-
|
|
3032
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3033
|
-
const now = new Date().toISOString();
|
|
3034
|
-
const current = await this.getRecord(id);
|
|
3035
|
-
if (current.version !== expectedVersion) {
|
|
3036
|
-
throw new error_types_1.OptimisticLockError('Record', id, {
|
|
3037
|
-
expectedVersion,
|
|
3038
|
-
actualVersion: current.version,
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
|
-
const updateData = {};
|
|
3042
|
-
if (data.data !== undefined) {
|
|
3043
|
-
updateData.data = JSON.stringify(data.data);
|
|
3044
|
-
}
|
|
3045
|
-
if (data.type !== undefined) {
|
|
3046
|
-
updateData.type = data.type;
|
|
3047
|
-
}
|
|
3048
|
-
await this.db
|
|
3049
|
-
.update(records)
|
|
3050
|
-
.set({ ...updateData, version: expectedVersion + 1, updatedAt: now })
|
|
3051
|
-
.where(eq(records.id, id));
|
|
3052
|
-
if (data.tags !== undefined) {
|
|
3053
|
-
await this.db.delete(recordTags).where(eq(recordTags.recordId, id));
|
|
3054
|
-
if (data.tags.length > 0) {
|
|
3055
|
-
const { epics } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3056
|
-
const epic = await this.db
|
|
3057
|
-
.select()
|
|
3058
|
-
.from(epics)
|
|
3059
|
-
.where(eq(epics.id, current.epicId))
|
|
3060
|
-
.limit(1);
|
|
3061
|
-
const projectId = epic[0]?.projectId || null;
|
|
3062
|
-
for (const tagName of data.tags) {
|
|
3063
|
-
const { and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3064
|
-
let tag = await this.db
|
|
3065
|
-
.select()
|
|
3066
|
-
.from(tags)
|
|
3067
|
-
.where(and(eq(tags.name, tagName), or(eq(tags.projectId, projectId || ''), isNull(tags.projectId))))
|
|
3068
|
-
.limit(1);
|
|
3069
|
-
if (!tag[0]) {
|
|
3070
|
-
const newTag = await this.createTag({ projectId, name: tagName });
|
|
3071
|
-
tag = [newTag];
|
|
3072
|
-
}
|
|
3073
|
-
await this.db.insert(recordTags).values({
|
|
3074
|
-
recordId: id,
|
|
3075
|
-
tagId: tag[0].id,
|
|
3076
|
-
createdAt: now,
|
|
3077
|
-
});
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
logger.info({ recordId: id }, 'Updated record');
|
|
3082
|
-
return this.getRecord(id);
|
|
457
|
+
return this.recordDelegate.updateRecord(id, data, expectedVersion);
|
|
3083
458
|
}
|
|
3084
459
|
async deleteRecord(id) {
|
|
3085
|
-
|
|
3086
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3087
|
-
await this.db.delete(records).where(eq(records.id, id));
|
|
3088
|
-
logger.info({ recordId: id }, 'Deleted record');
|
|
460
|
+
return this.recordDelegate.deleteRecord(id);
|
|
3089
461
|
}
|
|
3090
462
|
async markMessageAsRead(messageId, agentId, readAt) {
|
|
3091
|
-
|
|
3092
|
-
const { eq, and } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3093
|
-
const existing = await this.db
|
|
3094
|
-
.select()
|
|
3095
|
-
.from(chatMessageReads)
|
|
3096
|
-
.where(and(eq(chatMessageReads.messageId, messageId), eq(chatMessageReads.agentId, agentId)))
|
|
3097
|
-
.limit(1);
|
|
3098
|
-
if (!existing[0]) {
|
|
3099
|
-
await this.db.insert(chatMessageReads).values({
|
|
3100
|
-
messageId,
|
|
3101
|
-
agentId,
|
|
3102
|
-
readAt,
|
|
3103
|
-
});
|
|
3104
|
-
logger.info({ messageId, agentId }, 'Marked message as read');
|
|
3105
|
-
}
|
|
3106
|
-
else {
|
|
3107
|
-
logger.debug({ messageId, agentId }, 'Message already marked as read');
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
normalizeTagList(tags) {
|
|
3111
|
-
if (!tags?.length) {
|
|
3112
|
-
return [];
|
|
3113
|
-
}
|
|
3114
|
-
const unique = new Set();
|
|
3115
|
-
for (const tag of tags) {
|
|
3116
|
-
const trimmed = tag.trim();
|
|
3117
|
-
if (trimmed) {
|
|
3118
|
-
unique.add(trimmed);
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
return Array.from(unique);
|
|
3122
|
-
}
|
|
3123
|
-
slugify(value) {
|
|
3124
|
-
return value
|
|
3125
|
-
.trim()
|
|
3126
|
-
.toLowerCase()
|
|
3127
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
3128
|
-
.replace(/-{2,}/g, '-')
|
|
3129
|
-
.replace(/^-+|-+$/g, '');
|
|
463
|
+
return this.reviewDelegate.markMessageAsRead(messageId, agentId, readAt);
|
|
3130
464
|
}
|
|
3131
465
|
async generateDocumentSlug(projectId, desired, excludeId) {
|
|
3132
|
-
|
|
3133
|
-
const { eq, and, isNull, ne } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3134
|
-
const base = this.slugify(desired || 'document') || 'document';
|
|
3135
|
-
let candidate = base;
|
|
3136
|
-
let attempt = 1;
|
|
3137
|
-
while (true) {
|
|
3138
|
-
const projectCondition = projectId === null ? isNull(documents.projectId) : eq(documents.projectId, projectId);
|
|
3139
|
-
const slugCondition = eq(documents.slug, candidate);
|
|
3140
|
-
const whereClause = excludeId
|
|
3141
|
-
? and(slugCondition, projectCondition, ne(documents.id, excludeId))
|
|
3142
|
-
: and(slugCondition, projectCondition);
|
|
3143
|
-
const existing = await this.db
|
|
3144
|
-
.select({ id: documents.id })
|
|
3145
|
-
.from(documents)
|
|
3146
|
-
.where(whereClause)
|
|
3147
|
-
.limit(1);
|
|
3148
|
-
if (!existing[0]) {
|
|
3149
|
-
return candidate;
|
|
3150
|
-
}
|
|
3151
|
-
attempt += 1;
|
|
3152
|
-
candidate = `${base}-${attempt}`;
|
|
3153
|
-
}
|
|
466
|
+
return this.documentDelegate.generateDocumentSlug(projectId, desired, excludeId);
|
|
3154
467
|
}
|
|
3155
468
|
async setDocumentTags(documentId, tagNames, projectId) {
|
|
3156
|
-
|
|
3157
|
-
const { documentTags, tags } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3158
|
-
const { eq, and, or, isNull } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3159
|
-
await this.db.delete(documentTags).where(eq(documentTags.documentId, documentId));
|
|
3160
|
-
for (const tagName of normalizedTags) {
|
|
3161
|
-
const projectCondition = projectId === null
|
|
3162
|
-
? isNull(tags.projectId)
|
|
3163
|
-
: or(eq(tags.projectId, projectId), isNull(tags.projectId));
|
|
3164
|
-
const existing = await this.db
|
|
3165
|
-
.select()
|
|
3166
|
-
.from(tags)
|
|
3167
|
-
.where(and(eq(tags.name, tagName), projectCondition))
|
|
3168
|
-
.limit(1);
|
|
3169
|
-
let tagId = existing[0]?.id;
|
|
3170
|
-
if (!tagId) {
|
|
3171
|
-
const newTag = await this.createTag({ projectId, name: tagName });
|
|
3172
|
-
tagId = newTag.id;
|
|
3173
|
-
}
|
|
3174
|
-
await this.db.insert(documentTags).values({
|
|
3175
|
-
documentId,
|
|
3176
|
-
tagId,
|
|
3177
|
-
});
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
async convertLegacyIdleWatcher(watcher) {
|
|
3181
|
-
const rawCondition = watcher.condition;
|
|
3182
|
-
if (rawCondition?.type !== 'idle') {
|
|
3183
|
-
return watcher;
|
|
3184
|
-
}
|
|
3185
|
-
const parsedIdleAfterSeconds = Number.parseInt(rawCondition.pattern ?? '', 10);
|
|
3186
|
-
const idleAfterSeconds = Number.isFinite(parsedIdleAfterSeconds) && parsedIdleAfterSeconds > 0
|
|
3187
|
-
? parsedIdleAfterSeconds
|
|
3188
|
-
: 0;
|
|
3189
|
-
const convertedCondition = { type: 'regex', pattern: '.*' };
|
|
3190
|
-
const convertedWatcher = {
|
|
3191
|
-
...watcher,
|
|
3192
|
-
idleAfterSeconds,
|
|
3193
|
-
condition: convertedCondition,
|
|
3194
|
-
};
|
|
3195
|
-
logger.warn({
|
|
3196
|
-
watcherId: watcher.id,
|
|
3197
|
-
projectId: watcher.projectId,
|
|
3198
|
-
legacyCondition: rawCondition,
|
|
3199
|
-
idleAfterSeconds,
|
|
3200
|
-
}, 'Converted legacy idle watcher condition to idleAfterSeconds');
|
|
3201
|
-
try {
|
|
3202
|
-
const { terminalWatchers } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3203
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3204
|
-
await this.db
|
|
3205
|
-
.update(terminalWatchers)
|
|
3206
|
-
.set({
|
|
3207
|
-
idleAfterSeconds,
|
|
3208
|
-
condition: convertedCondition,
|
|
3209
|
-
updatedAt: new Date().toISOString(),
|
|
3210
|
-
})
|
|
3211
|
-
.where(eq(terminalWatchers.id, watcher.id));
|
|
3212
|
-
}
|
|
3213
|
-
catch (error) {
|
|
3214
|
-
logger.warn({ watcherId: watcher.id, error: String(error) }, 'Failed to persist converted legacy idle watcher');
|
|
3215
|
-
}
|
|
3216
|
-
return convertedWatcher;
|
|
469
|
+
return this.documentDelegate.setDocumentTags(documentId, tagNames, projectId);
|
|
3217
470
|
}
|
|
3218
471
|
async listWatchers(projectId) {
|
|
3219
|
-
|
|
3220
|
-
const { eq, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3221
|
-
const rows = await this.db
|
|
3222
|
-
.select()
|
|
3223
|
-
.from(terminalWatchers)
|
|
3224
|
-
.where(eq(terminalWatchers.projectId, projectId))
|
|
3225
|
-
.orderBy(desc(terminalWatchers.createdAt));
|
|
3226
|
-
const mappedWatchers = rows.map((row) => ({
|
|
3227
|
-
...row,
|
|
3228
|
-
condition: row.condition,
|
|
3229
|
-
}));
|
|
3230
|
-
return Promise.all(mappedWatchers.map((watcher) => this.convertLegacyIdleWatcher(watcher)));
|
|
472
|
+
return this.watcherDelegate.listWatchers(projectId);
|
|
3231
473
|
}
|
|
3232
474
|
async getWatcher(id) {
|
|
3233
|
-
|
|
3234
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3235
|
-
const result = await this.db
|
|
3236
|
-
.select()
|
|
3237
|
-
.from(terminalWatchers)
|
|
3238
|
-
.where(eq(terminalWatchers.id, id))
|
|
3239
|
-
.limit(1);
|
|
3240
|
-
if (!result[0]) {
|
|
3241
|
-
return null;
|
|
3242
|
-
}
|
|
3243
|
-
const watcher = {
|
|
3244
|
-
...result[0],
|
|
3245
|
-
condition: result[0].condition,
|
|
3246
|
-
};
|
|
3247
|
-
return this.convertLegacyIdleWatcher(watcher);
|
|
475
|
+
return this.watcherDelegate.getWatcher(id);
|
|
3248
476
|
}
|
|
3249
477
|
async createWatcher(data) {
|
|
3250
|
-
|
|
3251
|
-
const now = new Date().toISOString();
|
|
3252
|
-
const { terminalWatchers } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3253
|
-
const watcher = {
|
|
3254
|
-
id: randomUUID(),
|
|
3255
|
-
...data,
|
|
3256
|
-
createdAt: now,
|
|
3257
|
-
updatedAt: now,
|
|
3258
|
-
};
|
|
3259
|
-
await this.db.insert(terminalWatchers).values({
|
|
3260
|
-
id: watcher.id,
|
|
3261
|
-
projectId: watcher.projectId,
|
|
3262
|
-
name: watcher.name,
|
|
3263
|
-
description: watcher.description,
|
|
3264
|
-
enabled: watcher.enabled,
|
|
3265
|
-
scope: watcher.scope,
|
|
3266
|
-
scopeFilterId: watcher.scopeFilterId,
|
|
3267
|
-
pollIntervalMs: watcher.pollIntervalMs,
|
|
3268
|
-
viewportLines: watcher.viewportLines,
|
|
3269
|
-
idleAfterSeconds: watcher.idleAfterSeconds,
|
|
3270
|
-
condition: watcher.condition,
|
|
3271
|
-
cooldownMs: watcher.cooldownMs,
|
|
3272
|
-
cooldownMode: watcher.cooldownMode,
|
|
3273
|
-
eventName: watcher.eventName,
|
|
3274
|
-
createdAt: watcher.createdAt,
|
|
3275
|
-
updatedAt: watcher.updatedAt,
|
|
3276
|
-
});
|
|
3277
|
-
return watcher;
|
|
478
|
+
return this.watcherDelegate.createWatcher(data);
|
|
3278
479
|
}
|
|
3279
480
|
async updateWatcher(id, data) {
|
|
3280
|
-
|
|
3281
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3282
|
-
const now = new Date().toISOString();
|
|
3283
|
-
const existing = await this.getWatcher(id);
|
|
3284
|
-
if (!existing) {
|
|
3285
|
-
throw new error_types_1.NotFoundError('Watcher', id);
|
|
3286
|
-
}
|
|
3287
|
-
await this.db
|
|
3288
|
-
.update(terminalWatchers)
|
|
3289
|
-
.set({ ...data, updatedAt: now })
|
|
3290
|
-
.where(eq(terminalWatchers.id, id));
|
|
3291
|
-
const updated = await this.getWatcher(id);
|
|
3292
|
-
if (!updated) {
|
|
3293
|
-
throw new error_types_1.NotFoundError('Watcher', id);
|
|
3294
|
-
}
|
|
3295
|
-
return updated;
|
|
481
|
+
return this.watcherDelegate.updateWatcher(id, data);
|
|
3296
482
|
}
|
|
3297
483
|
async deleteWatcher(id) {
|
|
3298
|
-
|
|
3299
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3300
|
-
await this.db.delete(terminalWatchers).where(eq(terminalWatchers.id, id));
|
|
484
|
+
return this.watcherDelegate.deleteWatcher(id);
|
|
3301
485
|
}
|
|
3302
486
|
async listEnabledWatchers() {
|
|
3303
|
-
|
|
3304
|
-
const { eq, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3305
|
-
const rows = await this.db
|
|
3306
|
-
.select()
|
|
3307
|
-
.from(terminalWatchers)
|
|
3308
|
-
.where(eq(terminalWatchers.enabled, true))
|
|
3309
|
-
.orderBy(desc(terminalWatchers.createdAt));
|
|
3310
|
-
const mappedWatchers = rows.map((row) => ({
|
|
3311
|
-
...row,
|
|
3312
|
-
condition: row.condition,
|
|
3313
|
-
}));
|
|
3314
|
-
return Promise.all(mappedWatchers.map((watcher) => this.convertLegacyIdleWatcher(watcher)));
|
|
487
|
+
return this.watcherDelegate.listEnabledWatchers();
|
|
3315
488
|
}
|
|
3316
489
|
async listSubscribers(projectId) {
|
|
3317
|
-
|
|
3318
|
-
const { eq, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3319
|
-
const rows = await this.db
|
|
3320
|
-
.select()
|
|
3321
|
-
.from(automationSubscribers)
|
|
3322
|
-
.where(eq(automationSubscribers.projectId, projectId))
|
|
3323
|
-
.orderBy(desc(automationSubscribers.createdAt));
|
|
3324
|
-
return rows.map((row) => ({
|
|
3325
|
-
...row,
|
|
3326
|
-
eventFilter: row.eventFilter,
|
|
3327
|
-
actionInputs: row.actionInputs,
|
|
3328
|
-
}));
|
|
490
|
+
return this.subscriberDelegate.listSubscribers(projectId);
|
|
3329
491
|
}
|
|
3330
492
|
async getSubscriber(id) {
|
|
3331
|
-
|
|
3332
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3333
|
-
const result = await this.db
|
|
3334
|
-
.select()
|
|
3335
|
-
.from(automationSubscribers)
|
|
3336
|
-
.where(eq(automationSubscribers.id, id))
|
|
3337
|
-
.limit(1);
|
|
3338
|
-
if (!result[0]) {
|
|
3339
|
-
return null;
|
|
3340
|
-
}
|
|
3341
|
-
return {
|
|
3342
|
-
...result[0],
|
|
3343
|
-
eventFilter: result[0].eventFilter,
|
|
3344
|
-
actionInputs: result[0].actionInputs,
|
|
3345
|
-
};
|
|
493
|
+
return this.subscriberDelegate.getSubscriber(id);
|
|
3346
494
|
}
|
|
3347
495
|
async createSubscriber(data) {
|
|
3348
|
-
|
|
3349
|
-
const now = new Date().toISOString();
|
|
3350
|
-
const { automationSubscribers } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3351
|
-
const subscriber = {
|
|
3352
|
-
id: randomUUID(),
|
|
3353
|
-
...data,
|
|
3354
|
-
createdAt: now,
|
|
3355
|
-
updatedAt: now,
|
|
3356
|
-
};
|
|
3357
|
-
await this.db.insert(automationSubscribers).values({
|
|
3358
|
-
id: subscriber.id,
|
|
3359
|
-
projectId: subscriber.projectId,
|
|
3360
|
-
name: subscriber.name,
|
|
3361
|
-
description: subscriber.description,
|
|
3362
|
-
enabled: subscriber.enabled,
|
|
3363
|
-
eventName: subscriber.eventName,
|
|
3364
|
-
eventFilter: subscriber.eventFilter,
|
|
3365
|
-
actionType: subscriber.actionType,
|
|
3366
|
-
actionInputs: subscriber.actionInputs,
|
|
3367
|
-
delayMs: subscriber.delayMs,
|
|
3368
|
-
cooldownMs: subscriber.cooldownMs,
|
|
3369
|
-
retryOnError: subscriber.retryOnError,
|
|
3370
|
-
groupName: subscriber.groupName,
|
|
3371
|
-
position: subscriber.position,
|
|
3372
|
-
priority: subscriber.priority,
|
|
3373
|
-
createdAt: subscriber.createdAt,
|
|
3374
|
-
updatedAt: subscriber.updatedAt,
|
|
3375
|
-
});
|
|
3376
|
-
return subscriber;
|
|
496
|
+
return this.subscriberDelegate.createSubscriber(data);
|
|
3377
497
|
}
|
|
3378
498
|
async updateSubscriber(id, data) {
|
|
3379
|
-
|
|
3380
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3381
|
-
const now = new Date().toISOString();
|
|
3382
|
-
const existing = await this.getSubscriber(id);
|
|
3383
|
-
if (!existing) {
|
|
3384
|
-
throw new error_types_1.NotFoundError('Subscriber', id);
|
|
3385
|
-
}
|
|
3386
|
-
await this.db
|
|
3387
|
-
.update(automationSubscribers)
|
|
3388
|
-
.set({ ...data, updatedAt: now })
|
|
3389
|
-
.where(eq(automationSubscribers.id, id));
|
|
3390
|
-
const updated = await this.getSubscriber(id);
|
|
3391
|
-
if (!updated) {
|
|
3392
|
-
throw new error_types_1.NotFoundError('Subscriber', id);
|
|
3393
|
-
}
|
|
3394
|
-
return updated;
|
|
499
|
+
return this.subscriberDelegate.updateSubscriber(id, data);
|
|
3395
500
|
}
|
|
3396
501
|
async deleteSubscriber(id) {
|
|
3397
|
-
|
|
3398
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3399
|
-
await this.db.delete(automationSubscribers).where(eq(automationSubscribers.id, id));
|
|
502
|
+
return this.subscriberDelegate.deleteSubscriber(id);
|
|
3400
503
|
}
|
|
3401
504
|
async findSubscribersByEventName(projectId, eventName) {
|
|
3402
|
-
|
|
3403
|
-
const { eq, and, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3404
|
-
const rows = await this.db
|
|
3405
|
-
.select()
|
|
3406
|
-
.from(automationSubscribers)
|
|
3407
|
-
.where(and(eq(automationSubscribers.projectId, projectId), eq(automationSubscribers.eventName, eventName), eq(automationSubscribers.enabled, true)))
|
|
3408
|
-
.orderBy(desc(automationSubscribers.createdAt));
|
|
3409
|
-
return rows.map((row) => ({
|
|
3410
|
-
...row,
|
|
3411
|
-
eventFilter: row.eventFilter,
|
|
3412
|
-
actionInputs: row.actionInputs,
|
|
3413
|
-
}));
|
|
505
|
+
return this.subscriberDelegate.findSubscribersByEventName(projectId, eventName);
|
|
3414
506
|
}
|
|
3415
507
|
async getProjectByRootPath(rootPath) {
|
|
3416
|
-
|
|
3417
|
-
const { projects } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3418
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3419
|
-
const normalizedPath = resolve(rootPath);
|
|
3420
|
-
const rows = await this.db
|
|
3421
|
-
.select()
|
|
3422
|
-
.from(projects)
|
|
3423
|
-
.where(eq(projects.rootPath, normalizedPath))
|
|
3424
|
-
.limit(1);
|
|
3425
|
-
if (rows.length === 0) {
|
|
3426
|
-
return null;
|
|
3427
|
-
}
|
|
3428
|
-
return {
|
|
3429
|
-
id: rows[0].id,
|
|
3430
|
-
name: rows[0].name,
|
|
3431
|
-
description: rows[0].description,
|
|
3432
|
-
rootPath: rows[0].rootPath,
|
|
3433
|
-
isTemplate: rows[0].isTemplate,
|
|
3434
|
-
createdAt: rows[0].createdAt,
|
|
3435
|
-
updatedAt: rows[0].updatedAt,
|
|
3436
|
-
};
|
|
508
|
+
return this.projectDelegate.getProjectByRootPath(rootPath);
|
|
3437
509
|
}
|
|
3438
510
|
async findProjectContainingPath(absolutePath) {
|
|
3439
|
-
|
|
3440
|
-
const { projects } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3441
|
-
const normalizedPath = resolve(absolutePath);
|
|
3442
|
-
const allProjects = [];
|
|
3443
|
-
const pageSize = 100;
|
|
3444
|
-
let offset = 0;
|
|
3445
|
-
let hasMore = true;
|
|
3446
|
-
while (hasMore) {
|
|
3447
|
-
const rows = await this.db.select().from(projects).limit(pageSize).offset(offset);
|
|
3448
|
-
if (rows.length === 0) {
|
|
3449
|
-
hasMore = false;
|
|
3450
|
-
}
|
|
3451
|
-
else {
|
|
3452
|
-
for (const row of rows) {
|
|
3453
|
-
allProjects.push({
|
|
3454
|
-
id: row.id,
|
|
3455
|
-
name: row.name,
|
|
3456
|
-
description: row.description,
|
|
3457
|
-
rootPath: row.rootPath,
|
|
3458
|
-
isTemplate: row.isTemplate,
|
|
3459
|
-
createdAt: row.createdAt,
|
|
3460
|
-
updatedAt: row.updatedAt,
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
offset += pageSize;
|
|
3464
|
-
if (rows.length < pageSize) {
|
|
3465
|
-
hasMore = false;
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
let bestMatch = null;
|
|
3470
|
-
let longestRootPath = 0;
|
|
3471
|
-
for (const project of allProjects) {
|
|
3472
|
-
const projectRoot = resolve(project.rootPath);
|
|
3473
|
-
if (normalizedPath === projectRoot || normalizedPath.startsWith(projectRoot + sep)) {
|
|
3474
|
-
if (projectRoot.length > longestRootPath) {
|
|
3475
|
-
longestRootPath = projectRoot.length;
|
|
3476
|
-
bestMatch = project;
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
}
|
|
3480
|
-
return bestMatch;
|
|
511
|
+
return this.projectDelegate.findProjectContainingPath(absolutePath);
|
|
3481
512
|
}
|
|
3482
513
|
async createGuest(data) {
|
|
3483
|
-
|
|
3484
|
-
const { guests } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3485
|
-
const { eq, and, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3486
|
-
const now = new Date().toISOString();
|
|
3487
|
-
const existingByName = await this.db
|
|
3488
|
-
.select()
|
|
3489
|
-
.from(guests)
|
|
3490
|
-
.where(and(eq(guests.projectId, data.projectId), sql `${guests.name} = ${data.name} COLLATE NOCASE`))
|
|
3491
|
-
.limit(1);
|
|
3492
|
-
if (existingByName.length > 0) {
|
|
3493
|
-
throw new error_types_1.ConflictError(`Guest with name "${data.name}" already exists in project`, {
|
|
3494
|
-
projectId: data.projectId,
|
|
3495
|
-
name: data.name,
|
|
3496
|
-
});
|
|
3497
|
-
}
|
|
3498
|
-
const existingByTmux = await this.db
|
|
3499
|
-
.select()
|
|
3500
|
-
.from(guests)
|
|
3501
|
-
.where(eq(guests.tmuxSessionId, data.tmuxSessionId))
|
|
3502
|
-
.limit(1);
|
|
3503
|
-
if (existingByTmux.length > 0) {
|
|
3504
|
-
throw new error_types_1.ConflictError(`Guest with tmux session "${data.tmuxSessionId}" already exists`, {
|
|
3505
|
-
tmuxSessionId: data.tmuxSessionId,
|
|
3506
|
-
});
|
|
3507
|
-
}
|
|
3508
|
-
const guest = {
|
|
3509
|
-
id: randomUUID(),
|
|
3510
|
-
projectId: data.projectId,
|
|
3511
|
-
name: data.name,
|
|
3512
|
-
description: data.description ?? null,
|
|
3513
|
-
tmuxSessionId: data.tmuxSessionId,
|
|
3514
|
-
lastSeenAt: data.lastSeenAt,
|
|
3515
|
-
createdAt: now,
|
|
3516
|
-
updatedAt: now,
|
|
3517
|
-
};
|
|
3518
|
-
await this.db.insert(guests).values(guest);
|
|
3519
|
-
logger.info({ guestId: guest.id, projectId: data.projectId, name: data.name }, 'Created guest');
|
|
3520
|
-
return guest;
|
|
514
|
+
return this.guestDelegate.createGuest(data);
|
|
3521
515
|
}
|
|
3522
516
|
async getGuest(id) {
|
|
3523
|
-
|
|
3524
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3525
|
-
const rows = await this.db.select().from(guests).where(eq(guests.id, id)).limit(1);
|
|
3526
|
-
if (rows.length === 0) {
|
|
3527
|
-
throw new error_types_1.NotFoundError('Guest', id);
|
|
3528
|
-
}
|
|
3529
|
-
return rows[0];
|
|
517
|
+
return this.guestDelegate.getGuest(id);
|
|
3530
518
|
}
|
|
3531
519
|
async getGuestByName(projectId, name) {
|
|
3532
|
-
|
|
3533
|
-
const { eq, and, sql } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3534
|
-
const rows = await this.db
|
|
3535
|
-
.select()
|
|
3536
|
-
.from(guests)
|
|
3537
|
-
.where(and(eq(guests.projectId, projectId), sql `${guests.name} = ${name} COLLATE NOCASE`))
|
|
3538
|
-
.limit(1);
|
|
3539
|
-
if (rows.length === 0) {
|
|
3540
|
-
return null;
|
|
3541
|
-
}
|
|
3542
|
-
return rows[0];
|
|
520
|
+
return this.guestDelegate.getGuestByName(projectId, name);
|
|
3543
521
|
}
|
|
3544
522
|
async getGuestByTmuxSessionId(tmuxSessionId) {
|
|
3545
|
-
|
|
3546
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3547
|
-
const rows = await this.db
|
|
3548
|
-
.select()
|
|
3549
|
-
.from(guests)
|
|
3550
|
-
.where(eq(guests.tmuxSessionId, tmuxSessionId))
|
|
3551
|
-
.limit(1);
|
|
3552
|
-
if (rows.length === 0) {
|
|
3553
|
-
return null;
|
|
3554
|
-
}
|
|
3555
|
-
return rows[0];
|
|
523
|
+
return this.guestDelegate.getGuestByTmuxSessionId(tmuxSessionId);
|
|
3556
524
|
}
|
|
3557
525
|
async getGuestsByIdPrefix(prefix) {
|
|
3558
|
-
|
|
3559
|
-
const { like } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3560
|
-
const rows = await this.db
|
|
3561
|
-
.select()
|
|
3562
|
-
.from(guests)
|
|
3563
|
-
.where(like(guests.id, `${prefix}%`));
|
|
3564
|
-
return rows;
|
|
526
|
+
return this.guestDelegate.getGuestsByIdPrefix(prefix);
|
|
3565
527
|
}
|
|
3566
528
|
async listGuests(projectId) {
|
|
3567
|
-
|
|
3568
|
-
const { eq, asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3569
|
-
const rows = await this.db
|
|
3570
|
-
.select()
|
|
3571
|
-
.from(guests)
|
|
3572
|
-
.where(eq(guests.projectId, projectId))
|
|
3573
|
-
.orderBy(asc(guests.name));
|
|
3574
|
-
return rows;
|
|
529
|
+
return this.guestDelegate.listGuests(projectId);
|
|
3575
530
|
}
|
|
3576
531
|
async listAllGuests() {
|
|
3577
|
-
|
|
3578
|
-
const { asc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3579
|
-
const rows = await this.db.select().from(guests).orderBy(asc(guests.createdAt));
|
|
3580
|
-
return rows;
|
|
532
|
+
return this.guestDelegate.listAllGuests();
|
|
3581
533
|
}
|
|
3582
534
|
async deleteGuest(id) {
|
|
3583
|
-
|
|
3584
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3585
|
-
await this.getGuest(id);
|
|
3586
|
-
await this.db.delete(guests).where(eq(guests.id, id));
|
|
3587
|
-
logger.info({ guestId: id }, 'Deleted guest');
|
|
535
|
+
return this.guestDelegate.deleteGuest(id);
|
|
3588
536
|
}
|
|
3589
537
|
async updateGuestLastSeen(id, lastSeenAt) {
|
|
3590
|
-
|
|
3591
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3592
|
-
const existing = await this.getGuest(id);
|
|
3593
|
-
const now = new Date().toISOString();
|
|
3594
|
-
await this.db
|
|
3595
|
-
.update(guests)
|
|
3596
|
-
.set({
|
|
3597
|
-
lastSeenAt,
|
|
3598
|
-
updatedAt: now,
|
|
3599
|
-
})
|
|
3600
|
-
.where(eq(guests.id, id));
|
|
3601
|
-
return {
|
|
3602
|
-
...existing,
|
|
3603
|
-
lastSeenAt,
|
|
3604
|
-
updatedAt: now,
|
|
3605
|
-
};
|
|
538
|
+
return this.guestDelegate.updateGuestLastSeen(id, lastSeenAt);
|
|
3606
539
|
}
|
|
3607
540
|
async createReview(data) {
|
|
3608
|
-
|
|
3609
|
-
const now = new Date().toISOString();
|
|
3610
|
-
const { reviews } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3611
|
-
const review = {
|
|
3612
|
-
id: randomUUID(),
|
|
3613
|
-
projectId: data.projectId,
|
|
3614
|
-
epicId: data.epicId,
|
|
3615
|
-
title: data.title,
|
|
3616
|
-
description: data.description,
|
|
3617
|
-
status: data.status,
|
|
3618
|
-
mode: data.mode,
|
|
3619
|
-
baseRef: data.baseRef,
|
|
3620
|
-
headRef: data.headRef,
|
|
3621
|
-
baseSha: data.baseSha,
|
|
3622
|
-
headSha: data.headSha,
|
|
3623
|
-
createdBy: data.createdBy,
|
|
3624
|
-
createdByAgentId: data.createdByAgentId,
|
|
3625
|
-
version: 1,
|
|
3626
|
-
createdAt: now,
|
|
3627
|
-
updatedAt: now,
|
|
3628
|
-
};
|
|
3629
|
-
await this.db.insert(reviews).values({
|
|
3630
|
-
id: review.id,
|
|
3631
|
-
projectId: review.projectId,
|
|
3632
|
-
epicId: review.epicId,
|
|
3633
|
-
title: review.title,
|
|
3634
|
-
description: review.description,
|
|
3635
|
-
status: review.status,
|
|
3636
|
-
mode: review.mode,
|
|
3637
|
-
baseRef: review.baseRef,
|
|
3638
|
-
headRef: review.headRef,
|
|
3639
|
-
baseSha: review.baseSha,
|
|
3640
|
-
headSha: review.headSha,
|
|
3641
|
-
createdBy: review.createdBy,
|
|
3642
|
-
createdByAgentId: review.createdByAgentId,
|
|
3643
|
-
version: review.version,
|
|
3644
|
-
createdAt: review.createdAt,
|
|
3645
|
-
updatedAt: review.updatedAt,
|
|
3646
|
-
});
|
|
3647
|
-
logger.info({ reviewId: review.id, projectId: review.projectId }, 'Created review');
|
|
3648
|
-
return review;
|
|
541
|
+
return this.reviewDelegate.createReview(data);
|
|
3649
542
|
}
|
|
3650
543
|
async getReview(id) {
|
|
3651
|
-
|
|
3652
|
-
const { eq, count } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3653
|
-
const result = await this.db.select().from(reviews).where(eq(reviews.id, id)).limit(1);
|
|
3654
|
-
if (!result[0]) {
|
|
3655
|
-
throw new error_types_1.NotFoundError('Review', id);
|
|
3656
|
-
}
|
|
3657
|
-
const countResult = await this.db
|
|
3658
|
-
.select({ count: count() })
|
|
3659
|
-
.from(reviewComments)
|
|
3660
|
-
.where(eq(reviewComments.reviewId, id));
|
|
3661
|
-
return {
|
|
3662
|
-
...result[0],
|
|
3663
|
-
commentCount: countResult[0]?.count ?? 0,
|
|
3664
|
-
};
|
|
544
|
+
return this.reviewDelegate.getReview(id);
|
|
3665
545
|
}
|
|
3666
546
|
async updateReview(id, data, expectedVersion) {
|
|
3667
|
-
|
|
3668
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3669
|
-
const now = new Date().toISOString();
|
|
3670
|
-
const current = await this.getReview(id);
|
|
3671
|
-
if (current.version !== expectedVersion) {
|
|
3672
|
-
throw new error_types_1.OptimisticLockError('Review', id, {
|
|
3673
|
-
expectedVersion,
|
|
3674
|
-
actualVersion: current.version,
|
|
3675
|
-
});
|
|
3676
|
-
}
|
|
3677
|
-
const updateData = {};
|
|
3678
|
-
if (data.title !== undefined)
|
|
3679
|
-
updateData.title = data.title;
|
|
3680
|
-
if (data.description !== undefined)
|
|
3681
|
-
updateData.description = data.description;
|
|
3682
|
-
if (data.status !== undefined)
|
|
3683
|
-
updateData.status = data.status;
|
|
3684
|
-
if (data.headSha !== undefined)
|
|
3685
|
-
updateData.headSha = data.headSha;
|
|
3686
|
-
await this.db
|
|
3687
|
-
.update(reviews)
|
|
3688
|
-
.set({ ...updateData, version: expectedVersion + 1, updatedAt: now })
|
|
3689
|
-
.where(eq(reviews.id, id));
|
|
3690
|
-
logger.info({ reviewId: id }, 'Updated review');
|
|
3691
|
-
return this.getReview(id);
|
|
547
|
+
return this.reviewDelegate.updateReview(id, data, expectedVersion);
|
|
3692
548
|
}
|
|
3693
549
|
async deleteReview(id) {
|
|
3694
|
-
|
|
3695
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3696
|
-
await this.db.delete(reviews).where(eq(reviews.id, id));
|
|
3697
|
-
logger.info({ reviewId: id }, 'Deleted review');
|
|
550
|
+
return this.reviewDelegate.deleteReview(id);
|
|
3698
551
|
}
|
|
3699
552
|
async listReviews(projectId, options = {}) {
|
|
3700
|
-
|
|
3701
|
-
const { eq, and, count, desc } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3702
|
-
const limit = options.limit ?? 100;
|
|
3703
|
-
const offset = options.offset ?? 0;
|
|
3704
|
-
const conditions = [eq(reviews.projectId, projectId)];
|
|
3705
|
-
if (options.status) {
|
|
3706
|
-
conditions.push(eq(reviews.status, options.status));
|
|
3707
|
-
}
|
|
3708
|
-
if (options.epicId) {
|
|
3709
|
-
conditions.push(eq(reviews.epicId, options.epicId));
|
|
3710
|
-
}
|
|
3711
|
-
const rows = await this.db
|
|
3712
|
-
.select()
|
|
3713
|
-
.from(reviews)
|
|
3714
|
-
.where(and(...conditions))
|
|
3715
|
-
.orderBy(desc(reviews.createdAt))
|
|
3716
|
-
.limit(limit)
|
|
3717
|
-
.offset(offset);
|
|
3718
|
-
const reviewIds = rows.map((r) => r.id);
|
|
3719
|
-
let commentCountMap = new Map();
|
|
3720
|
-
if (reviewIds.length > 0) {
|
|
3721
|
-
const { inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3722
|
-
const countRows = await this.db
|
|
3723
|
-
.select({
|
|
3724
|
-
reviewId: reviewComments.reviewId,
|
|
3725
|
-
count: count(),
|
|
3726
|
-
})
|
|
3727
|
-
.from(reviewComments)
|
|
3728
|
-
.where(inArray(reviewComments.reviewId, reviewIds))
|
|
3729
|
-
.groupBy(reviewComments.reviewId);
|
|
3730
|
-
commentCountMap = new Map(countRows.map((r) => [r.reviewId, r.count]));
|
|
3731
|
-
}
|
|
3732
|
-
const items = rows.map((row) => ({
|
|
3733
|
-
...row,
|
|
3734
|
-
commentCount: commentCountMap.get(row.id) ?? 0,
|
|
3735
|
-
}));
|
|
3736
|
-
return {
|
|
3737
|
-
items,
|
|
3738
|
-
total: items.length,
|
|
3739
|
-
limit,
|
|
3740
|
-
offset,
|
|
3741
|
-
};
|
|
553
|
+
return this.reviewDelegate.listReviews(projectId, options);
|
|
3742
554
|
}
|
|
3743
555
|
async createReviewComment(data, targetAgentIds) {
|
|
3744
|
-
|
|
3745
|
-
const now = new Date().toISOString();
|
|
3746
|
-
const { reviewComments, reviewCommentTargets } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3747
|
-
const sqlite = (0, sqlite_raw_1.getRawSqliteClient)(this.db);
|
|
3748
|
-
if (!sqlite || typeof sqlite.exec !== 'function') {
|
|
3749
|
-
throw new error_types_1.StorageError('Unable to access underlying SQLite client');
|
|
3750
|
-
}
|
|
3751
|
-
sqlite.exec('BEGIN IMMEDIATE TRANSACTION');
|
|
3752
|
-
try {
|
|
3753
|
-
const comment = {
|
|
3754
|
-
id: randomUUID(),
|
|
3755
|
-
reviewId: data.reviewId,
|
|
3756
|
-
filePath: data.filePath,
|
|
3757
|
-
parentId: data.parentId,
|
|
3758
|
-
lineStart: data.lineStart,
|
|
3759
|
-
lineEnd: data.lineEnd,
|
|
3760
|
-
side: data.side,
|
|
3761
|
-
content: data.content,
|
|
3762
|
-
commentType: data.commentType,
|
|
3763
|
-
status: data.status,
|
|
3764
|
-
authorType: data.authorType,
|
|
3765
|
-
authorAgentId: data.authorAgentId,
|
|
3766
|
-
version: 1,
|
|
3767
|
-
editedAt: null,
|
|
3768
|
-
createdAt: now,
|
|
3769
|
-
updatedAt: now,
|
|
3770
|
-
};
|
|
3771
|
-
await this.db.insert(reviewComments).values({
|
|
3772
|
-
id: comment.id,
|
|
3773
|
-
reviewId: comment.reviewId,
|
|
3774
|
-
filePath: comment.filePath,
|
|
3775
|
-
parentId: comment.parentId,
|
|
3776
|
-
lineStart: comment.lineStart,
|
|
3777
|
-
lineEnd: comment.lineEnd,
|
|
3778
|
-
side: comment.side,
|
|
3779
|
-
content: comment.content,
|
|
3780
|
-
commentType: comment.commentType,
|
|
3781
|
-
status: comment.status,
|
|
3782
|
-
authorType: comment.authorType,
|
|
3783
|
-
authorAgentId: comment.authorAgentId,
|
|
3784
|
-
version: comment.version,
|
|
3785
|
-
editedAt: comment.editedAt,
|
|
3786
|
-
createdAt: comment.createdAt,
|
|
3787
|
-
updatedAt: comment.updatedAt,
|
|
3788
|
-
});
|
|
3789
|
-
if (targetAgentIds && targetAgentIds.length > 0) {
|
|
3790
|
-
for (const agentId of targetAgentIds) {
|
|
3791
|
-
await this.db.insert(reviewCommentTargets).values({
|
|
3792
|
-
id: randomUUID(),
|
|
3793
|
-
commentId: comment.id,
|
|
3794
|
-
agentId,
|
|
3795
|
-
createdAt: now,
|
|
3796
|
-
});
|
|
3797
|
-
}
|
|
3798
|
-
}
|
|
3799
|
-
sqlite.exec('COMMIT');
|
|
3800
|
-
logger.info({ commentId: comment.id, reviewId: comment.reviewId, targets: targetAgentIds?.length ?? 0 }, 'Created review comment');
|
|
3801
|
-
return comment;
|
|
3802
|
-
}
|
|
3803
|
-
catch (error) {
|
|
3804
|
-
try {
|
|
3805
|
-
sqlite.exec('ROLLBACK');
|
|
3806
|
-
logger.info('Transaction rolled back successfully');
|
|
3807
|
-
}
|
|
3808
|
-
catch (rollbackError) {
|
|
3809
|
-
logger.error({ rollbackError }, 'Failed to rollback transaction');
|
|
3810
|
-
}
|
|
3811
|
-
throw error;
|
|
3812
|
-
}
|
|
556
|
+
return this.reviewDelegate.createReviewComment(data, targetAgentIds);
|
|
3813
557
|
}
|
|
3814
558
|
async getReviewComment(id) {
|
|
3815
|
-
|
|
3816
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3817
|
-
const result = await this.db
|
|
3818
|
-
.select()
|
|
3819
|
-
.from(reviewComments)
|
|
3820
|
-
.where(eq(reviewComments.id, id))
|
|
3821
|
-
.limit(1);
|
|
3822
|
-
if (!result[0]) {
|
|
3823
|
-
throw new error_types_1.NotFoundError('ReviewComment', id);
|
|
3824
|
-
}
|
|
3825
|
-
return result[0];
|
|
559
|
+
return this.reviewDelegate.getReviewComment(id);
|
|
3826
560
|
}
|
|
3827
561
|
async updateReviewComment(id, data, expectedVersion) {
|
|
3828
|
-
|
|
3829
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3830
|
-
const now = new Date().toISOString();
|
|
3831
|
-
const current = await this.getReviewComment(id);
|
|
3832
|
-
if (current.version !== expectedVersion) {
|
|
3833
|
-
throw new error_types_1.OptimisticLockError('ReviewComment', id, {
|
|
3834
|
-
expectedVersion,
|
|
3835
|
-
actualVersion: current.version,
|
|
3836
|
-
});
|
|
3837
|
-
}
|
|
3838
|
-
const updateData = {};
|
|
3839
|
-
if (data.content !== undefined && data.content !== current.content) {
|
|
3840
|
-
updateData.content = data.content;
|
|
3841
|
-
updateData.editedAt = now;
|
|
3842
|
-
}
|
|
3843
|
-
if (data.status !== undefined && data.status !== current.status) {
|
|
3844
|
-
updateData.status = data.status;
|
|
3845
|
-
}
|
|
3846
|
-
if (Object.keys(updateData).length === 0) {
|
|
3847
|
-
logger.info({ commentId: id }, 'Skipped review comment update (no changes)');
|
|
3848
|
-
return current;
|
|
3849
|
-
}
|
|
3850
|
-
await this.db
|
|
3851
|
-
.update(reviewComments)
|
|
3852
|
-
.set({ ...updateData, version: expectedVersion + 1, updatedAt: now })
|
|
3853
|
-
.where(eq(reviewComments.id, id));
|
|
3854
|
-
logger.info({ commentId: id }, 'Updated review comment');
|
|
3855
|
-
return this.getReviewComment(id);
|
|
562
|
+
return this.reviewDelegate.updateReviewComment(id, data, expectedVersion);
|
|
3856
563
|
}
|
|
3857
564
|
async listReviewComments(reviewId, options = {}) {
|
|
3858
|
-
|
|
3859
|
-
const { eq, and, isNull, desc, inArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3860
|
-
const limit = options.limit ?? 100;
|
|
3861
|
-
const offset = options.offset ?? 0;
|
|
3862
|
-
const conditions = [eq(reviewComments.reviewId, reviewId)];
|
|
3863
|
-
if (options.status) {
|
|
3864
|
-
conditions.push(eq(reviewComments.status, options.status));
|
|
3865
|
-
}
|
|
3866
|
-
if (options.filePath) {
|
|
3867
|
-
conditions.push(eq(reviewComments.filePath, options.filePath));
|
|
3868
|
-
}
|
|
3869
|
-
if (options.parentId === null) {
|
|
3870
|
-
conditions.push(isNull(reviewComments.parentId));
|
|
3871
|
-
}
|
|
3872
|
-
else if (options.parentId !== undefined) {
|
|
3873
|
-
conditions.push(eq(reviewComments.parentId, options.parentId));
|
|
3874
|
-
}
|
|
3875
|
-
const rows = await this.db
|
|
3876
|
-
.select()
|
|
3877
|
-
.from(reviewComments)
|
|
3878
|
-
.where(and(...conditions))
|
|
3879
|
-
.orderBy(desc(reviewComments.createdAt))
|
|
3880
|
-
.limit(limit)
|
|
3881
|
-
.offset(offset);
|
|
3882
|
-
if (rows.length === 0) {
|
|
3883
|
-
return { items: [], total: 0, limit, offset };
|
|
3884
|
-
}
|
|
3885
|
-
const authorAgentIds = [
|
|
3886
|
-
...new Set(rows.map((r) => r.authorAgentId).filter((id) => typeof id === 'string')),
|
|
3887
|
-
];
|
|
3888
|
-
const agentNameMap = new Map();
|
|
3889
|
-
if (authorAgentIds.length > 0) {
|
|
3890
|
-
const authorAgents = await this.db
|
|
3891
|
-
.select({ id: agents.id, name: agents.name })
|
|
3892
|
-
.from(agents)
|
|
3893
|
-
.where(inArray(agents.id, authorAgentIds));
|
|
3894
|
-
authorAgents.forEach((a) => agentNameMap.set(a.id, a.name));
|
|
3895
|
-
}
|
|
3896
|
-
const commentIds = rows.map((r) => r.id);
|
|
3897
|
-
const targetsWithNames = await this.db
|
|
3898
|
-
.select({
|
|
3899
|
-
commentId: reviewCommentTargets.commentId,
|
|
3900
|
-
agentId: reviewCommentTargets.agentId,
|
|
3901
|
-
agentName: agents.name,
|
|
3902
|
-
})
|
|
3903
|
-
.from(reviewCommentTargets)
|
|
3904
|
-
.leftJoin(agents, eq(reviewCommentTargets.agentId, agents.id))
|
|
3905
|
-
.where(inArray(reviewCommentTargets.commentId, commentIds));
|
|
3906
|
-
const targetsByCommentId = new Map();
|
|
3907
|
-
targetsWithNames.forEach((t) => {
|
|
3908
|
-
const list = targetsByCommentId.get(t.commentId) ?? [];
|
|
3909
|
-
list.push({ agentId: t.agentId, name: t.agentName ?? 'Unknown' });
|
|
3910
|
-
targetsByCommentId.set(t.commentId, list);
|
|
3911
|
-
});
|
|
3912
|
-
const enrichedItems = rows.map((row) => ({
|
|
3913
|
-
...row,
|
|
3914
|
-
authorAgentName: row.authorAgentId ? (agentNameMap.get(row.authorAgentId) ?? null) : null,
|
|
3915
|
-
targetAgents: targetsByCommentId.get(row.id) ?? [],
|
|
3916
|
-
}));
|
|
3917
|
-
return {
|
|
3918
|
-
items: enrichedItems,
|
|
3919
|
-
total: rows.length,
|
|
3920
|
-
limit,
|
|
3921
|
-
offset,
|
|
3922
|
-
};
|
|
565
|
+
return this.reviewDelegate.listReviewComments(reviewId, options);
|
|
3923
566
|
}
|
|
3924
567
|
async addReviewCommentTargets(commentId, agentIds) {
|
|
3925
|
-
|
|
3926
|
-
const now = new Date().toISOString();
|
|
3927
|
-
const { reviewCommentTargets } = await Promise.resolve().then(() => __importStar(require('../db/schema')));
|
|
3928
|
-
await this.getReviewComment(commentId);
|
|
3929
|
-
const targets = [];
|
|
3930
|
-
for (const agentId of agentIds) {
|
|
3931
|
-
const target = {
|
|
3932
|
-
id: randomUUID(),
|
|
3933
|
-
commentId,
|
|
3934
|
-
agentId,
|
|
3935
|
-
createdAt: now,
|
|
3936
|
-
};
|
|
3937
|
-
await this.db.insert(reviewCommentTargets).values(target);
|
|
3938
|
-
targets.push(target);
|
|
3939
|
-
}
|
|
3940
|
-
logger.info({ commentId, count: targets.length }, 'Added review comment targets');
|
|
3941
|
-
return targets;
|
|
568
|
+
return this.reviewDelegate.addReviewCommentTargets(commentId, agentIds);
|
|
3942
569
|
}
|
|
3943
570
|
async getReviewCommentTargets(commentId) {
|
|
3944
|
-
|
|
3945
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3946
|
-
const rows = await this.db
|
|
3947
|
-
.select()
|
|
3948
|
-
.from(reviewCommentTargets)
|
|
3949
|
-
.where(eq(reviewCommentTargets.commentId, commentId));
|
|
3950
|
-
return rows;
|
|
571
|
+
return this.reviewDelegate.getReviewCommentTargets(commentId);
|
|
3951
572
|
}
|
|
3952
573
|
async deleteReviewComment(id) {
|
|
3953
|
-
|
|
3954
|
-
const { eq } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3955
|
-
await this.db.delete(reviewComments).where(eq(reviewComments.id, id));
|
|
574
|
+
return this.reviewDelegate.deleteReviewComment(id);
|
|
3956
575
|
}
|
|
3957
576
|
async deleteNonResolvedComments(reviewId) {
|
|
3958
|
-
|
|
3959
|
-
const { eq, and, notInArray } = await Promise.resolve().then(() => __importStar(require('drizzle-orm')));
|
|
3960
|
-
const result = await this.db
|
|
3961
|
-
.delete(reviewComments)
|
|
3962
|
-
.where(and(eq(reviewComments.reviewId, reviewId), notInArray(reviewComments.status, ['resolved', 'wont_fix'])));
|
|
3963
|
-
return result.changes ?? 0;
|
|
577
|
+
return this.reviewDelegate.deleteNonResolvedComments(reviewId);
|
|
3964
578
|
}
|
|
3965
579
|
};
|
|
3966
580
|
exports.LocalStorageService = LocalStorageService;
|
|
@@ -3969,18 +583,4 @@ exports.LocalStorageService = LocalStorageService = __decorate([
|
|
|
3969
583
|
__param(0, (0, common_1.Inject)(db_provider_1.DB_CONNECTION)),
|
|
3970
584
|
__metadata("design:paramtypes", [better_sqlite3_1.BetterSQLite3Database])
|
|
3971
585
|
], LocalStorageService);
|
|
3972
|
-
function safePreview(v) {
|
|
3973
|
-
try {
|
|
3974
|
-
if (v === undefined)
|
|
3975
|
-
return 'undefined';
|
|
3976
|
-
if (v === null)
|
|
3977
|
-
return 'null';
|
|
3978
|
-
if (typeof v === 'string')
|
|
3979
|
-
return v.length > 200 ? v.slice(0, 200) + '…' : v;
|
|
3980
|
-
return JSON.stringify(v).slice(0, 200);
|
|
3981
|
-
}
|
|
3982
|
-
catch {
|
|
3983
|
-
return '[unserializable]';
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
586
|
//# sourceMappingURL=local-storage.service.js.map
|