crewly 1.20.132 → 1.20.134
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/constants.ts +49 -0
- package/config/index.ts +2 -0
- package/dist/backend/backend/src/constants.d.ts +82 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +87 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts +56 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js +114 -0
- package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +25 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +73 -55
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts +3 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +9 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +14 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts +115 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js +160 -0
- package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +13 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.js +5 -2
- package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/backend/config/constants.d.ts +48 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +48 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/backend/config/index.d.ts +2 -2
- package/dist/backend/config/index.d.ts.map +1 -1
- package/dist/backend/config/index.js +2 -2
- package/dist/backend/config/index.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +82 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +87 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
- package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
- package/dist/cli/backend/src/services/core/storage.service.js +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.js +20 -0
- package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.js +5 -2
- package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.js +10 -0
- package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
- package/dist/cli/cli/src/commands/cloud.d.ts +45 -25
- package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/cloud.js +153 -47
- package/dist/cli/cli/src/commands/cloud.js.map +1 -1
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
- package/dist/cli/cli/src/commands/onboard-checklist.js +3 -3
- package/dist/cli/cli/src/commands/onboard-checklist.js.map +1 -1
- package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
- package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/onboard.js +80 -3
- package/dist/cli/cli/src/commands/onboard.js.map +1 -1
- package/dist/cli/cli/src/constants.d.ts +5 -0
- package/dist/cli/cli/src/constants.d.ts.map +1 -1
- package/dist/cli/cli/src/constants.js +5 -0
- package/dist/cli/cli/src/constants.js.map +1 -1
- package/dist/cli/cli/src/index.js +21 -4
- package/dist/cli/cli/src/index.js.map +1 -1
- package/dist/cli/cli/src/utils/cloud-submit.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/cloud-submit.js +14 -5
- package/dist/cli/cli/src/utils/cloud-submit.js.map +1 -1
- package/dist/cli/config/constants.d.ts +48 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +48 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/dist/cli/config/index.d.ts +2 -2
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -2
- package/dist/cli/config/index.js.map +1 -1
- package/frontend/dist/assets/{index-5b98bf77.js → index-97bb17d4.js} +274 -274
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution bundle controller — REST handlers for `/api/bundles`
|
|
3
|
+
* (specs/solution-bundles.md).
|
|
4
|
+
*
|
|
5
|
+
* Every response is `{ success: true, data }` or
|
|
6
|
+
* `{ success: false, error, code?, missing?, invalid? }`.
|
|
7
|
+
*
|
|
8
|
+
* Owner-only: applying a bundle and reading an apply job refuse a request
|
|
9
|
+
* that carries `X-Agent-Session` (403), like `/api/harness` and the setup
|
|
10
|
+
* checklist — an agent must not deploy teams, schedules and channels in the
|
|
11
|
+
* owner's name. The catalog reads stay open.
|
|
12
|
+
*
|
|
13
|
+
* @module controllers/bundle/bundle.controller
|
|
14
|
+
*/
|
|
15
|
+
import { refuseAgent } from '../harness/harness.controller.js';
|
|
16
|
+
import { BundleError } from '../../services/bundle/bundle-apply.service.js';
|
|
17
|
+
import { toBundleDetail, toBundleSummary } from '../../services/bundle/bundle-catalog.js';
|
|
18
|
+
/** HTTP status per engine error code. */
|
|
19
|
+
const ERROR_STATUS = {
|
|
20
|
+
unknown_bundle: 404,
|
|
21
|
+
job_not_found: 404,
|
|
22
|
+
bundle_not_ready: 409,
|
|
23
|
+
invalid_runtime: 400,
|
|
24
|
+
invalid_answers: 400,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Send an error response.
|
|
28
|
+
*
|
|
29
|
+
* @param res - Response
|
|
30
|
+
* @param error - Thrown value
|
|
31
|
+
*/
|
|
32
|
+
export function sendBundleError(res, error) {
|
|
33
|
+
if (error instanceof BundleError) {
|
|
34
|
+
res.status(ERROR_STATUS[error.code] ?? 400).json({
|
|
35
|
+
success: false,
|
|
36
|
+
error: error.message,
|
|
37
|
+
code: error.code,
|
|
38
|
+
...(error.details ? { missing: error.details.missing, invalid: error.details.invalid } : {}),
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A route parameter as a string.
|
|
46
|
+
*
|
|
47
|
+
* @param req - Request
|
|
48
|
+
* @param name - Parameter
|
|
49
|
+
* @returns Value ('' when absent)
|
|
50
|
+
*/
|
|
51
|
+
function param(req, name) {
|
|
52
|
+
const value = req.params[name];
|
|
53
|
+
return typeof value === 'string' ? value : '';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the handlers.
|
|
57
|
+
*
|
|
58
|
+
* @param deps - Engine and catalog accessors (tests inject fakes)
|
|
59
|
+
* @returns Handlers
|
|
60
|
+
*/
|
|
61
|
+
export function createBundleController(deps) {
|
|
62
|
+
return {
|
|
63
|
+
/** GET / — ready bundles (`?drafts=1` includes drafts) */
|
|
64
|
+
async list(req, res) {
|
|
65
|
+
try {
|
|
66
|
+
const includeDrafts = req.query.drafts === '1' || req.query.drafts === 'true';
|
|
67
|
+
const bundles = deps.catalog().list({ includeDrafts }).map((b) => toBundleSummary(b.template));
|
|
68
|
+
res.json({ success: true, data: { bundles } });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
sendBundleError(res, error);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
/** GET /:templateId — questions, members, what gets set up, and this machine's deployment */
|
|
75
|
+
async detail(req, res) {
|
|
76
|
+
try {
|
|
77
|
+
const id = param(req, 'templateId');
|
|
78
|
+
const entry = deps.catalog().get(id);
|
|
79
|
+
if (!entry)
|
|
80
|
+
throw new BundleError('unknown_bundle', `没有找到方案「${id}」`);
|
|
81
|
+
const deployment = await deps.service().getDeployment(id);
|
|
82
|
+
res.json({ success: true, data: { bundle: toBundleDetail(entry.template), deployment } });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
sendBundleError(res, error);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
/** POST /apply { templateId, answers, runtime?, allowDraft? } — start (or join) an apply job; 202 */
|
|
89
|
+
async apply(req, res) {
|
|
90
|
+
if (refuseAgent(req, res, 'deploy a solution bundle'))
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const body = (req.body ?? {});
|
|
94
|
+
const templateId = typeof body.templateId === 'string' ? body.templateId.trim() : '';
|
|
95
|
+
if (!templateId)
|
|
96
|
+
throw new BundleError('unknown_bundle', 'templateId is required');
|
|
97
|
+
const deployment = await deps.service().start({
|
|
98
|
+
templateId,
|
|
99
|
+
answers: body.answers,
|
|
100
|
+
runtime: typeof body.runtime === 'string' && body.runtime ? body.runtime : undefined,
|
|
101
|
+
allowDraft: body.allowDraft === true,
|
|
102
|
+
});
|
|
103
|
+
res.status(202).json({ success: true, data: { jobId: deployment.jobId, deployment } });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
sendBundleError(res, error);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
/** GET /apply/:jobId — progress of an apply job */
|
|
110
|
+
async getJob(req, res) {
|
|
111
|
+
if (refuseAgent(req, res, 'read a bundle deployment'))
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
res.json({ success: true, data: await deps.service().getJob(param(req, 'jobId')) });
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
sendBundleError(res, error);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=bundle.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.controller.js","sourceRoot":"","sources":["../../../../../../backend/src/controllers/bundle/bundle.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAiD,MAAM,+CAA+C,CAAC;AAC3H,OAAO,EAAE,cAAc,EAAE,eAAe,EAAsB,MAAM,yCAAyC,CAAC;AAE9G,yCAAyC;AACzC,MAAM,YAAY,GAAoC;IACpD,cAAc,EAAE,GAAG;IACnB,aAAa,EAAE,GAAG;IAClB,gBAAgB,EAAE,GAAG;IACrB,eAAe,EAAE,GAAG;IACpB,eAAe,EAAE,GAAG;CACrB,CAAC;AAgBF;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa,EAAE,KAAc;IAC3D,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;QACjC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;YAC/C,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK,CAAC,OAAO;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7F,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC1G,CAAC;AAED;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,GAAY,EAAE,IAAY;IACvC,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAA0B;IAC/D,OAAO;QACL,0DAA0D;QAC1D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG;YACjB,IAAI,CAAC;gBACH,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC;gBAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC/F,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YACjD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,6FAA6F;QAC7F,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG;YACnB,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBACpC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACrC,IAAI,CAAC,KAAK;oBAAE,MAAM,IAAI,WAAW,CAAC,gBAAgB,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;gBACrE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;gBAC1D,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YAC5F,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,qGAAqG;QACrG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG;YAClB,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,0BAA0B,CAAC;gBAAE,OAAO;YAC9D,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAC;gBACzD,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrF,IAAI,CAAC,UAAU;oBAAE,MAAM,IAAI,WAAW,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;gBACnF,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC;oBAC5C,UAAU;oBACV,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;oBACpF,UAAU,EAAE,IAAI,CAAC,UAAU,KAAK,IAAI;iBACrC,CAAC,CAAC;gBACH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YACzF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG;YACnB,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,0BAA0B,CAAC;gBAAE,OAAO;YAC9D,IAAI,CAAC;gBACH,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution bundle routes — mounted at `/api/bundles`.
|
|
3
|
+
*
|
|
4
|
+
* GET and POST only, so the phone / portal relay can carry them
|
|
5
|
+
* (`MobileApiRelayService` allowlists `GET /bundles` and `POST /bundles/apply`).
|
|
6
|
+
*
|
|
7
|
+
* - GET / — ready bundles
|
|
8
|
+
* - GET /apply/:jobId — apply progress (owner-only)
|
|
9
|
+
* - POST /apply — deploy { templateId, answers, runtime?, allowDraft? } (owner-only)
|
|
10
|
+
* - GET /:templateId — questions, members, deployment on this machine
|
|
11
|
+
*
|
|
12
|
+
* @module controllers/bundle/bundle.routes
|
|
13
|
+
*/
|
|
14
|
+
import { Router } from 'express';
|
|
15
|
+
import type { BundleApplyService } from '../../services/bundle/bundle-apply.service.js';
|
|
16
|
+
import type { BundleCatalog } from '../../services/bundle/bundle-catalog.js';
|
|
17
|
+
/**
|
|
18
|
+
* Create the bundle router.
|
|
19
|
+
*
|
|
20
|
+
* @param getService - Engine accessor (tests inject a fake)
|
|
21
|
+
* @param getCatalog - Catalog accessor (tests inject a fake)
|
|
22
|
+
* @returns Express router
|
|
23
|
+
*/
|
|
24
|
+
export declare function createBundleRouter(getService?: () => BundleApplyService, getCatalog?: () => Pick<BundleCatalog, 'list' | 'get'>): Router;
|
|
25
|
+
//# sourceMappingURL=bundle.routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.routes.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/bundle/bundle.routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAI7E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,GAAE,MAAM,kBAA0C,EAC5D,UAAU,GAAE,MAAM,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,KAAK,CAAoB,GACvE,MAAM,CAQR"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution bundle routes — mounted at `/api/bundles`.
|
|
3
|
+
*
|
|
4
|
+
* GET and POST only, so the phone / portal relay can carry them
|
|
5
|
+
* (`MobileApiRelayService` allowlists `GET /bundles` and `POST /bundles/apply`).
|
|
6
|
+
*
|
|
7
|
+
* - GET / — ready bundles
|
|
8
|
+
* - GET /apply/:jobId — apply progress (owner-only)
|
|
9
|
+
* - POST /apply — deploy { templateId, answers, runtime?, allowDraft? } (owner-only)
|
|
10
|
+
* - GET /:templateId — questions, members, deployment on this machine
|
|
11
|
+
*
|
|
12
|
+
* @module controllers/bundle/bundle.routes
|
|
13
|
+
*/
|
|
14
|
+
import { Router } from 'express';
|
|
15
|
+
import { getBundleApplyService, getBundleCatalog } from '../../services/bundle/bundle-apply.factory.js';
|
|
16
|
+
import { createBundleController } from './bundle.controller.js';
|
|
17
|
+
/**
|
|
18
|
+
* Create the bundle router.
|
|
19
|
+
*
|
|
20
|
+
* @param getService - Engine accessor (tests inject a fake)
|
|
21
|
+
* @param getCatalog - Catalog accessor (tests inject a fake)
|
|
22
|
+
* @returns Express router
|
|
23
|
+
*/
|
|
24
|
+
export function createBundleRouter(getService = getBundleApplyService, getCatalog = getBundleCatalog) {
|
|
25
|
+
const router = Router();
|
|
26
|
+
const controller = createBundleController({ service: getService, catalog: getCatalog });
|
|
27
|
+
router.get('/', controller.list);
|
|
28
|
+
router.get('/apply/:jobId', controller.getJob);
|
|
29
|
+
router.post('/apply', controller.apply);
|
|
30
|
+
router.get('/:templateId', controller.detail);
|
|
31
|
+
return router;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=bundle.routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.routes.js","sourceRoot":"","sources":["../../../../../../backend/src/controllers/bundle/bundle.routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACxG,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAuC,qBAAqB,EAC5D,aAAwD,gBAAgB;IAExE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,UAAU,GAAG,sBAAsB,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IACxF,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud device pairing controller — `/api/cloud/device/*` on this backend.
|
|
3
|
+
*
|
|
4
|
+
* The web UI (`/setup` Cloud step, Settings → Cloud) and the phone app (over
|
|
5
|
+
* the relay allowlist) connect this machine to Crewly Cloud without handling
|
|
6
|
+
* tokens:
|
|
7
|
+
*
|
|
8
|
+
* - `POST /api/cloud/device/start` → `{ state: 'pending', userCode, verificationUrl, expiresAt, … }`
|
|
9
|
+
* (starts a pairing with crewly-auth, or returns the pending one).
|
|
10
|
+
* - `GET /api/cloud/device/status` → the same shape; `state` moves to
|
|
11
|
+
* `connected` once the owner approves and the backend has connected itself.
|
|
12
|
+
* - `POST /api/cloud/device/cancel` → stop waiting.
|
|
13
|
+
*
|
|
14
|
+
* Owner-only: every route refuses a request carrying `X-Agent-Session` (403)
|
|
15
|
+
* — an agent must not sign the machine in to an account, same rule as
|
|
16
|
+
* `/api/harness`. Responses never contain the device code or tokens.
|
|
17
|
+
*
|
|
18
|
+
* @module controllers/cloud/cloud-device-pairing.controller
|
|
19
|
+
*/
|
|
20
|
+
import type { Request, Response } from 'express';
|
|
21
|
+
import { CloudDevicePairingService } from '../../services/cloud/cloud-device-pairing.service.js';
|
|
22
|
+
/**
|
|
23
|
+
* The process-wide pairing service, wired to the real Cloud URL, device
|
|
24
|
+
* identity and connect path.
|
|
25
|
+
*
|
|
26
|
+
* @returns CloudDevicePairingService
|
|
27
|
+
*/
|
|
28
|
+
export declare function getCloudDevicePairingService(): CloudDevicePairingService;
|
|
29
|
+
/**
|
|
30
|
+
* Replace the service (tests).
|
|
31
|
+
*
|
|
32
|
+
* @param next - Service to use, or null to rebuild the real one
|
|
33
|
+
*/
|
|
34
|
+
export declare function setCloudDevicePairingServiceForTests(next: CloudDevicePairingService | null): void;
|
|
35
|
+
/**
|
|
36
|
+
* POST /api/cloud/device/start — begin (or resume) a pairing.
|
|
37
|
+
*
|
|
38
|
+
* @param req - Body `{ deviceName? }`
|
|
39
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
40
|
+
*/
|
|
41
|
+
export declare function startCloudDevicePairing(req: Request, res: Response): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* GET /api/cloud/device/status — where the pairing stands.
|
|
44
|
+
*
|
|
45
|
+
* @param req - Express request
|
|
46
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCloudDevicePairingStatus(req: Request, res: Response): void;
|
|
49
|
+
/**
|
|
50
|
+
* POST /api/cloud/device/cancel — stop waiting for the owner.
|
|
51
|
+
*
|
|
52
|
+
* @param req - Express request
|
|
53
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
54
|
+
*/
|
|
55
|
+
export declare function cancelCloudDevicePairing(req: Request, res: Response): void;
|
|
56
|
+
//# sourceMappingURL=cloud-device-pairing.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-device-pairing.controller.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/cloud/cloud-device-pairing.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAIjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sDAAsD,CAAC;AAcjG;;;;;GAKG;AACH,wBAAgB,4BAA4B,IAAI,yBAAyB,CAUxE;AAED;;;;GAIG;AACH,wBAAgB,oCAAoC,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,GAAG,IAAI,CAEjG;AAeD;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxF;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAI7E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAG1E"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud device pairing controller — `/api/cloud/device/*` on this backend.
|
|
3
|
+
*
|
|
4
|
+
* The web UI (`/setup` Cloud step, Settings → Cloud) and the phone app (over
|
|
5
|
+
* the relay allowlist) connect this machine to Crewly Cloud without handling
|
|
6
|
+
* tokens:
|
|
7
|
+
*
|
|
8
|
+
* - `POST /api/cloud/device/start` → `{ state: 'pending', userCode, verificationUrl, expiresAt, … }`
|
|
9
|
+
* (starts a pairing with crewly-auth, or returns the pending one).
|
|
10
|
+
* - `GET /api/cloud/device/status` → the same shape; `state` moves to
|
|
11
|
+
* `connected` once the owner approves and the backend has connected itself.
|
|
12
|
+
* - `POST /api/cloud/device/cancel` → stop waiting.
|
|
13
|
+
*
|
|
14
|
+
* Owner-only: every route refuses a request carrying `X-Agent-Session` (403)
|
|
15
|
+
* — an agent must not sign the machine in to an account, same rule as
|
|
16
|
+
* `/api/harness`. Responses never contain the device code or tokens.
|
|
17
|
+
*
|
|
18
|
+
* @module controllers/cloud/cloud-device-pairing.controller
|
|
19
|
+
*/
|
|
20
|
+
import { readAgentSessionHeader } from '../../utils/agent-caller.utils.js';
|
|
21
|
+
import { CLOUD_CONSTANTS } from '../../constants.js';
|
|
22
|
+
import { DeviceIdentityService } from '../../services/cloud/device-identity.service.js';
|
|
23
|
+
import { CloudDevicePairingService } from '../../services/cloud/cloud-device-pairing.service.js';
|
|
24
|
+
import { performCloudConnect } from './cloud.controller.js';
|
|
25
|
+
/** HTTP 403. */
|
|
26
|
+
const HTTP_FORBIDDEN = 403;
|
|
27
|
+
/** HTTP 502 — crewly-auth refused or was unreachable. */
|
|
28
|
+
const HTTP_BAD_GATEWAY = 502;
|
|
29
|
+
/** Longest device name accepted from the UI. */
|
|
30
|
+
const MAX_DEVICE_NAME_LENGTH = 80;
|
|
31
|
+
let service = null;
|
|
32
|
+
/**
|
|
33
|
+
* The process-wide pairing service, wired to the real Cloud URL, device
|
|
34
|
+
* identity and connect path.
|
|
35
|
+
*
|
|
36
|
+
* @returns CloudDevicePairingService
|
|
37
|
+
*/
|
|
38
|
+
export function getCloudDevicePairingService() {
|
|
39
|
+
if (!service) {
|
|
40
|
+
service = new CloudDevicePairingService({
|
|
41
|
+
cloudUrl: () => CLOUD_CONSTANTS.DEFAULT_CLOUD_URL,
|
|
42
|
+
identity: () => DeviceIdentityService.getInstance().getOrCreateIdentity(),
|
|
43
|
+
connect: (credentials) => performCloudConnect({ token: credentials.token, refreshToken: credentials.refreshToken }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return service;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Replace the service (tests).
|
|
50
|
+
*
|
|
51
|
+
* @param next - Service to use, or null to rebuild the real one
|
|
52
|
+
*/
|
|
53
|
+
export function setCloudDevicePairingServiceForTests(next) {
|
|
54
|
+
service = next;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Refuse a request made by an agent session.
|
|
58
|
+
*
|
|
59
|
+
* @param req - Express request
|
|
60
|
+
* @param res - Express response
|
|
61
|
+
* @returns True when refused (response sent)
|
|
62
|
+
*/
|
|
63
|
+
function refuseAgent(req, res) {
|
|
64
|
+
if (!readAgentSessionHeader(req))
|
|
65
|
+
return false;
|
|
66
|
+
res.status(HTTP_FORBIDDEN).json({ success: false, error: 'Only the owner can connect this machine to Crewly Cloud' });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* POST /api/cloud/device/start — begin (or resume) a pairing.
|
|
71
|
+
*
|
|
72
|
+
* @param req - Body `{ deviceName? }`
|
|
73
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
74
|
+
*/
|
|
75
|
+
export async function startCloudDevicePairing(req, res) {
|
|
76
|
+
if (refuseAgent(req, res))
|
|
77
|
+
return;
|
|
78
|
+
const raw = req.body?.deviceName;
|
|
79
|
+
const deviceName = typeof raw === 'string' ? raw.slice(0, MAX_DEVICE_NAME_LENGTH) : undefined;
|
|
80
|
+
try {
|
|
81
|
+
const status = await getCloudDevicePairingService().start({ deviceName });
|
|
82
|
+
res.json({ success: true, data: status });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
res.status(HTTP_BAD_GATEWAY).json({
|
|
86
|
+
success: false,
|
|
87
|
+
error: error instanceof Error ? error.message : 'Could not reach Crewly Cloud',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* GET /api/cloud/device/status — where the pairing stands.
|
|
93
|
+
*
|
|
94
|
+
* @param req - Express request
|
|
95
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
96
|
+
*/
|
|
97
|
+
export function getCloudDevicePairingStatus(req, res) {
|
|
98
|
+
if (refuseAgent(req, res))
|
|
99
|
+
return;
|
|
100
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
101
|
+
res.json({ success: true, data: getCloudDevicePairingService().getStatus() });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* POST /api/cloud/device/cancel — stop waiting for the owner.
|
|
105
|
+
*
|
|
106
|
+
* @param req - Express request
|
|
107
|
+
* @param res - `{ success, data: CloudDevicePairingStatus }`
|
|
108
|
+
*/
|
|
109
|
+
export function cancelCloudDevicePairing(req, res) {
|
|
110
|
+
if (refuseAgent(req, res))
|
|
111
|
+
return;
|
|
112
|
+
res.json({ success: true, data: getCloudDevicePairingService().cancel() });
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=cloud-device-pairing.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-device-pairing.controller.js","sourceRoot":"","sources":["../../../../../../backend/src/controllers/cloud/cloud-device-pairing.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,31 @@ export interface CloudHandshakePayload {
|
|
|
26
26
|
/** ISO timestamp of this handshake */
|
|
27
27
|
timestamp: string;
|
|
28
28
|
}
|
|
29
|
+
/** Input for {@link performCloudConnect}. */
|
|
30
|
+
export interface CloudConnectInput {
|
|
31
|
+
/** Cloud API base URL (defaults to CLOUD_CONSTANTS.DEFAULT_CLOUD_URL) */
|
|
32
|
+
cloudUrl?: string;
|
|
33
|
+
/** Cloud access token (JWT) */
|
|
34
|
+
token: string;
|
|
35
|
+
/** Cloud refresh token, for auto-renewal */
|
|
36
|
+
refreshToken?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Connect this instance to CrewlyAI Cloud with a token pair.
|
|
40
|
+
*
|
|
41
|
+
* The single connect path shared by `POST /api/cloud/connect` (CLI, pasted
|
|
42
|
+
* token, Google redirect) and the device-code pairing that completes in the
|
|
43
|
+
* background ({@link CloudDevicePairingService}): verify the token, persist
|
|
44
|
+
* the credentials, send the handshake, and start Cloud Sync, the message
|
|
45
|
+
* router and the Slack config sync.
|
|
46
|
+
*
|
|
47
|
+
* @param input - Cloud URL and token pair
|
|
48
|
+
* @returns The subscription tier
|
|
49
|
+
* @throws Error when the Cloud rejects the token (message contains "authentication failed")
|
|
50
|
+
*/
|
|
51
|
+
export declare function performCloudConnect(input: CloudConnectInput): Promise<{
|
|
52
|
+
tier: string;
|
|
53
|
+
}>;
|
|
29
54
|
/**
|
|
30
55
|
* POST /api/cloud/connect
|
|
31
56
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.controller.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/cloud/cloud.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAyB/D,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AA+ED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"cloud.controller.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/cloud/cloud.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -98,6 +98,78 @@ async function sendCloudHandshake(cloudUrl, token) {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Connect this instance to CrewlyAI Cloud with a token pair.
|
|
103
|
+
*
|
|
104
|
+
* The single connect path shared by `POST /api/cloud/connect` (CLI, pasted
|
|
105
|
+
* token, Google redirect) and the device-code pairing that completes in the
|
|
106
|
+
* background ({@link CloudDevicePairingService}): verify the token, persist
|
|
107
|
+
* the credentials, send the handshake, and start Cloud Sync, the message
|
|
108
|
+
* router and the Slack config sync.
|
|
109
|
+
*
|
|
110
|
+
* @param input - Cloud URL and token pair
|
|
111
|
+
* @returns The subscription tier
|
|
112
|
+
* @throws Error when the Cloud rejects the token (message contains "authentication failed")
|
|
113
|
+
*/
|
|
114
|
+
export async function performCloudConnect(input) {
|
|
115
|
+
const { cloudUrl, token, refreshToken } = input;
|
|
116
|
+
const resolvedUrl = cloudUrl || CLOUD_CONSTANTS.DEFAULT_CLOUD_URL;
|
|
117
|
+
const client = CloudClientService.getInstance();
|
|
118
|
+
// Try local JWT verification first — this works when OSS and Cloud share
|
|
119
|
+
// the same JWT secret (CREWLY_JWT_SECRET), or when the token was issued
|
|
120
|
+
// by this same instance (e.g. local Google OAuth flow).
|
|
121
|
+
const localPayload = verifyJwt(token);
|
|
122
|
+
let result;
|
|
123
|
+
if (localPayload) {
|
|
124
|
+
// JWT verified locally — connect without calling cloud API
|
|
125
|
+
const tier = localPayload.plan || 'free';
|
|
126
|
+
client.connectLocal(resolvedUrl, token, tier, refreshToken);
|
|
127
|
+
result = { success: true, tier };
|
|
128
|
+
logger.info('Connected to CrewlyAI Cloud (local JWT verification)', { tier });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Local verification failed (different JWT secret) — call cloud API
|
|
132
|
+
result = await client.connect(resolvedUrl, token, refreshToken);
|
|
133
|
+
logger.info('Connected to CrewlyAI Cloud (remote verification)', { tier: result.tier });
|
|
134
|
+
}
|
|
135
|
+
// Safety net: ensure refreshToken is persisted even if connect/connectLocal
|
|
136
|
+
// did not receive it (e.g. race condition or future refactor).
|
|
137
|
+
// Both connect() and connectLocal() already store refreshToken, but
|
|
138
|
+
// setRefreshToken() also re-persists config to disk as an extra guarantee.
|
|
139
|
+
if (refreshToken) {
|
|
140
|
+
client.setRefreshToken(refreshToken);
|
|
141
|
+
}
|
|
142
|
+
// Send device metadata + active teams to Cloud (best-effort, non-blocking)
|
|
143
|
+
sendCloudHandshake(resolvedUrl, token).catch(() => { });
|
|
144
|
+
// Start Cloud Sync for device discovery and message polling (best-effort)
|
|
145
|
+
try {
|
|
146
|
+
const identityService = DeviceIdentityService.getInstance();
|
|
147
|
+
const identity = await identityService.getOrCreateIdentity();
|
|
148
|
+
CloudSyncService.getInstance().start({
|
|
149
|
+
cloudUrl: resolvedUrl,
|
|
150
|
+
token,
|
|
151
|
+
deviceId: identity.deviceId,
|
|
152
|
+
deviceName: identity.deviceName,
|
|
153
|
+
});
|
|
154
|
+
logger.info('CloudSyncService started after cloud connect', { deviceId: identity.deviceId });
|
|
155
|
+
// Start MessageRouterService for cross-device agent communication
|
|
156
|
+
try {
|
|
157
|
+
const { startMessageRouter, startSlackCloudSync } = await import('../../services/cloud/cloud-initializer.js');
|
|
158
|
+
startMessageRouter();
|
|
159
|
+
// Slack v3: fetch the Cloud-owned Slack config right after login
|
|
160
|
+
startSlackCloudSync();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
logger.debug('MessageRouterService start deferred (non-fatal)');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (syncErr) {
|
|
167
|
+
logger.warn('CloudSyncService start failed (non-fatal)', {
|
|
168
|
+
error: syncErr instanceof Error ? syncErr.message : String(syncErr),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return { tier: result.tier };
|
|
172
|
+
}
|
|
101
173
|
/**
|
|
102
174
|
* POST /api/cloud/connect
|
|
103
175
|
*
|
|
@@ -116,61 +188,7 @@ export async function connectToCloud(req, res, next) {
|
|
|
116
188
|
res.status(400).json({ success: false, error: 'Missing required parameter: token' });
|
|
117
189
|
return;
|
|
118
190
|
}
|
|
119
|
-
const
|
|
120
|
-
const client = CloudClientService.getInstance();
|
|
121
|
-
// Try local JWT verification first — this works when OSS and Cloud share
|
|
122
|
-
// the same JWT secret (CREWLY_JWT_SECRET), or when the token was issued
|
|
123
|
-
// by this same instance (e.g. local Google OAuth flow).
|
|
124
|
-
const localPayload = verifyJwt(token);
|
|
125
|
-
let result;
|
|
126
|
-
if (localPayload) {
|
|
127
|
-
// JWT verified locally — connect without calling cloud API
|
|
128
|
-
const tier = localPayload.plan || 'free';
|
|
129
|
-
client.connectLocal(resolvedUrl, token, tier, refreshToken);
|
|
130
|
-
result = { success: true, tier };
|
|
131
|
-
logger.info('Connected to CrewlyAI Cloud (local JWT verification)', { tier });
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
// Local verification failed (different JWT secret) — call cloud API
|
|
135
|
-
result = await client.connect(resolvedUrl, token, refreshToken);
|
|
136
|
-
logger.info('Connected to CrewlyAI Cloud (remote verification)', { tier: result.tier });
|
|
137
|
-
}
|
|
138
|
-
// Safety net: ensure refreshToken is persisted even if connect/connectLocal
|
|
139
|
-
// did not receive it (e.g. race condition or future refactor).
|
|
140
|
-
// Both connect() and connectLocal() already store refreshToken, but
|
|
141
|
-
// setRefreshToken() also re-persists config to disk as an extra guarantee.
|
|
142
|
-
if (refreshToken) {
|
|
143
|
-
client.setRefreshToken(refreshToken);
|
|
144
|
-
}
|
|
145
|
-
// Send device metadata + active teams to Cloud (best-effort, non-blocking)
|
|
146
|
-
sendCloudHandshake(resolvedUrl, token).catch(() => { });
|
|
147
|
-
// Start Cloud Sync for device discovery and message polling (best-effort)
|
|
148
|
-
try {
|
|
149
|
-
const identityService = DeviceIdentityService.getInstance();
|
|
150
|
-
const identity = await identityService.getOrCreateIdentity();
|
|
151
|
-
CloudSyncService.getInstance().start({
|
|
152
|
-
cloudUrl: resolvedUrl,
|
|
153
|
-
token,
|
|
154
|
-
deviceId: identity.deviceId,
|
|
155
|
-
deviceName: identity.deviceName,
|
|
156
|
-
});
|
|
157
|
-
logger.info('CloudSyncService started after cloud connect', { deviceId: identity.deviceId });
|
|
158
|
-
// Start MessageRouterService for cross-device agent communication
|
|
159
|
-
try {
|
|
160
|
-
const { startMessageRouter, startSlackCloudSync } = await import('../../services/cloud/cloud-initializer.js');
|
|
161
|
-
startMessageRouter();
|
|
162
|
-
// Slack v3: fetch the Cloud-owned Slack config right after login
|
|
163
|
-
startSlackCloudSync();
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
logger.debug('MessageRouterService start deferred (non-fatal)');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
catch (syncErr) {
|
|
170
|
-
logger.warn('CloudSyncService start failed (non-fatal)', {
|
|
171
|
-
error: syncErr instanceof Error ? syncErr.message : String(syncErr),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
191
|
+
const result = await performCloudConnect({ cloudUrl, token, refreshToken });
|
|
174
192
|
res.json({ success: true, data: { tier: result.tier } });
|
|
175
193
|
}
|
|
176
194
|
catch (error) {
|