agent-relay 1.0.22 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bridge/shadow-cli.d.ts +17 -0
- package/dist/bridge/shadow-cli.d.ts.map +1 -0
- package/dist/bridge/shadow-cli.js +75 -0
- package/dist/bridge/shadow-cli.js.map +1 -0
- package/dist/bridge/shadow-config.d.ts +87 -0
- package/dist/bridge/shadow-config.d.ts.map +1 -0
- package/dist/bridge/shadow-config.js +134 -0
- package/dist/bridge/shadow-config.js.map +1 -0
- package/dist/bridge/spawner.d.ts +68 -1
- package/dist/bridge/spawner.d.ts.map +1 -1
- package/dist/bridge/spawner.js +360 -16
- package/dist/bridge/spawner.js.map +1 -1
- package/dist/bridge/types.d.ts +67 -0
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/cli/index.js +1196 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cloud/api/auth.d.ts +20 -0
- package/dist/cloud/api/auth.d.ts.map +1 -0
- package/dist/cloud/api/auth.js +128 -0
- package/dist/cloud/api/auth.js.map +1 -0
- package/dist/cloud/api/billing.d.ts +17 -0
- package/dist/cloud/api/billing.d.ts.map +1 -0
- package/dist/cloud/api/billing.js +353 -0
- package/dist/cloud/api/billing.js.map +1 -0
- package/dist/cloud/api/cli-pty-runner.d.ts +54 -0
- package/dist/cloud/api/cli-pty-runner.d.ts.map +1 -0
- package/dist/cloud/api/cli-pty-runner.js +119 -0
- package/dist/cloud/api/cli-pty-runner.js.map +1 -0
- package/dist/cloud/api/coordinators.d.ts +8 -0
- package/dist/cloud/api/coordinators.d.ts.map +1 -0
- package/dist/cloud/api/coordinators.js +347 -0
- package/dist/cloud/api/coordinators.js.map +1 -0
- package/dist/cloud/api/daemons.d.ts +12 -0
- package/dist/cloud/api/daemons.d.ts.map +1 -0
- package/dist/cloud/api/daemons.js +320 -0
- package/dist/cloud/api/daemons.js.map +1 -0
- package/dist/cloud/api/generic-webhooks.d.ts +8 -0
- package/dist/cloud/api/generic-webhooks.d.ts.map +1 -0
- package/dist/cloud/api/generic-webhooks.js +129 -0
- package/dist/cloud/api/generic-webhooks.js.map +1 -0
- package/dist/cloud/api/git.d.ts +8 -0
- package/dist/cloud/api/git.d.ts.map +1 -0
- package/dist/cloud/api/git.js +131 -0
- package/dist/cloud/api/git.js.map +1 -0
- package/dist/cloud/api/github-app.d.ts +11 -0
- package/dist/cloud/api/github-app.d.ts.map +1 -0
- package/dist/cloud/api/github-app.js +189 -0
- package/dist/cloud/api/github-app.js.map +1 -0
- package/dist/cloud/api/middleware/planLimits.d.ts +43 -0
- package/dist/cloud/api/middleware/planLimits.d.ts.map +1 -0
- package/dist/cloud/api/middleware/planLimits.js +202 -0
- package/dist/cloud/api/middleware/planLimits.js.map +1 -0
- package/dist/cloud/api/monitoring.d.ts +11 -0
- package/dist/cloud/api/monitoring.d.ts.map +1 -0
- package/dist/cloud/api/monitoring.js +578 -0
- package/dist/cloud/api/monitoring.js.map +1 -0
- package/dist/cloud/api/nango-auth.d.ts +9 -0
- package/dist/cloud/api/nango-auth.d.ts.map +1 -0
- package/dist/cloud/api/nango-auth.js +377 -0
- package/dist/cloud/api/nango-auth.js.map +1 -0
- package/dist/cloud/api/onboarding.d.ts +15 -0
- package/dist/cloud/api/onboarding.d.ts.map +1 -0
- package/dist/cloud/api/onboarding.js +588 -0
- package/dist/cloud/api/onboarding.js.map +1 -0
- package/dist/cloud/api/policy.d.ts +8 -0
- package/dist/cloud/api/policy.d.ts.map +1 -0
- package/dist/cloud/api/policy.js +229 -0
- package/dist/cloud/api/policy.js.map +1 -0
- package/dist/cloud/api/providers.d.ts +7 -0
- package/dist/cloud/api/providers.d.ts.map +1 -0
- package/dist/cloud/api/providers.js +507 -0
- package/dist/cloud/api/providers.js.map +1 -0
- package/dist/cloud/api/repos.d.ts +7 -0
- package/dist/cloud/api/repos.d.ts.map +1 -0
- package/dist/cloud/api/repos.js +314 -0
- package/dist/cloud/api/repos.js.map +1 -0
- package/dist/cloud/api/teams.d.ts +7 -0
- package/dist/cloud/api/teams.d.ts.map +1 -0
- package/dist/cloud/api/teams.js +279 -0
- package/dist/cloud/api/teams.js.map +1 -0
- package/dist/cloud/api/test-helpers.d.ts +10 -0
- package/dist/cloud/api/test-helpers.d.ts.map +1 -0
- package/dist/cloud/api/test-helpers.js +575 -0
- package/dist/cloud/api/test-helpers.js.map +1 -0
- package/dist/cloud/api/usage.d.ts +7 -0
- package/dist/cloud/api/usage.d.ts.map +1 -0
- package/dist/cloud/api/usage.js +98 -0
- package/dist/cloud/api/usage.js.map +1 -0
- package/dist/cloud/api/webhooks.d.ts +7 -0
- package/dist/cloud/api/webhooks.d.ts.map +1 -0
- package/dist/cloud/api/webhooks.js +496 -0
- package/dist/cloud/api/webhooks.js.map +1 -0
- package/dist/cloud/api/workspaces.d.ts +7 -0
- package/dist/cloud/api/workspaces.d.ts.map +1 -0
- package/dist/cloud/api/workspaces.js +727 -0
- package/dist/cloud/api/workspaces.js.map +1 -0
- package/dist/cloud/billing/index.d.ts +9 -0
- package/dist/cloud/billing/index.d.ts.map +1 -0
- package/dist/cloud/billing/index.js +9 -0
- package/dist/cloud/billing/index.js.map +1 -0
- package/dist/cloud/billing/plans.d.ts +39 -0
- package/dist/cloud/billing/plans.d.ts.map +1 -0
- package/dist/cloud/billing/plans.js +245 -0
- package/dist/cloud/billing/plans.js.map +1 -0
- package/dist/cloud/billing/service.d.ts +80 -0
- package/dist/cloud/billing/service.d.ts.map +1 -0
- package/dist/cloud/billing/service.js +388 -0
- package/dist/cloud/billing/service.js.map +1 -0
- package/dist/cloud/billing/types.d.ts +141 -0
- package/dist/cloud/billing/types.d.ts.map +1 -0
- package/dist/cloud/billing/types.js +7 -0
- package/dist/cloud/billing/types.js.map +1 -0
- package/dist/cloud/config.d.ts +66 -0
- package/dist/cloud/config.d.ts.map +1 -0
- package/dist/cloud/config.js +92 -0
- package/dist/cloud/config.js.map +1 -0
- package/dist/cloud/db/drizzle.d.ts +215 -0
- package/dist/cloud/db/drizzle.d.ts.map +1 -0
- package/dist/cloud/db/drizzle.js +1083 -0
- package/dist/cloud/db/drizzle.js.map +1 -0
- package/dist/cloud/db/index.d.ts +35 -0
- package/dist/cloud/db/index.d.ts.map +1 -0
- package/dist/cloud/db/index.js +52 -0
- package/dist/cloud/db/index.js.map +1 -0
- package/dist/cloud/db/schema.d.ts +4519 -0
- package/dist/cloud/db/schema.d.ts.map +1 -0
- package/dist/cloud/db/schema.js +547 -0
- package/dist/cloud/db/schema.js.map +1 -0
- package/dist/cloud/index.d.ts +12 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +39 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/cloud/provisioner/index.d.ts +75 -0
- package/dist/cloud/provisioner/index.d.ts.map +1 -0
- package/dist/cloud/provisioner/index.js +977 -0
- package/dist/cloud/provisioner/index.js.map +1 -0
- package/dist/cloud/server.d.ts +17 -0
- package/dist/cloud/server.d.ts.map +1 -0
- package/dist/cloud/server.js +534 -0
- package/dist/cloud/server.js.map +1 -0
- package/dist/cloud/services/auto-scaler.d.ts +152 -0
- package/dist/cloud/services/auto-scaler.d.ts.map +1 -0
- package/dist/cloud/services/auto-scaler.js +439 -0
- package/dist/cloud/services/auto-scaler.js.map +1 -0
- package/dist/cloud/services/capacity-manager.d.ts +148 -0
- package/dist/cloud/services/capacity-manager.d.ts.map +1 -0
- package/dist/cloud/services/capacity-manager.js +449 -0
- package/dist/cloud/services/capacity-manager.js.map +1 -0
- package/dist/cloud/services/ci-agent-spawner.d.ts +49 -0
- package/dist/cloud/services/ci-agent-spawner.d.ts.map +1 -0
- package/dist/cloud/services/ci-agent-spawner.js +373 -0
- package/dist/cloud/services/ci-agent-spawner.js.map +1 -0
- package/dist/cloud/services/coordinator.d.ts +62 -0
- package/dist/cloud/services/coordinator.d.ts.map +1 -0
- package/dist/cloud/services/coordinator.js +389 -0
- package/dist/cloud/services/coordinator.js.map +1 -0
- package/dist/cloud/services/index.d.ts +12 -0
- package/dist/cloud/services/index.d.ts.map +1 -0
- package/dist/cloud/services/index.js +15 -0
- package/dist/cloud/services/index.js.map +1 -0
- package/dist/cloud/services/mention-handler.d.ts +65 -0
- package/dist/cloud/services/mention-handler.d.ts.map +1 -0
- package/dist/cloud/services/mention-handler.js +405 -0
- package/dist/cloud/services/mention-handler.js.map +1 -0
- package/dist/cloud/services/nango.d.ts +126 -0
- package/dist/cloud/services/nango.d.ts.map +1 -0
- package/dist/cloud/services/nango.js +191 -0
- package/dist/cloud/services/nango.js.map +1 -0
- package/dist/cloud/services/persistence.d.ts +131 -0
- package/dist/cloud/services/persistence.d.ts.map +1 -0
- package/dist/cloud/services/persistence.js +200 -0
- package/dist/cloud/services/persistence.js.map +1 -0
- package/dist/cloud/services/planLimits.d.ts +125 -0
- package/dist/cloud/services/planLimits.d.ts.map +1 -0
- package/dist/cloud/services/planLimits.js +282 -0
- package/dist/cloud/services/planLimits.js.map +1 -0
- package/dist/cloud/services/scaling-orchestrator.d.ts +159 -0
- package/dist/cloud/services/scaling-orchestrator.d.ts.map +1 -0
- package/dist/cloud/services/scaling-orchestrator.js +502 -0
- package/dist/cloud/services/scaling-orchestrator.js.map +1 -0
- package/dist/cloud/services/scaling-policy.d.ts +121 -0
- package/dist/cloud/services/scaling-policy.d.ts.map +1 -0
- package/dist/cloud/services/scaling-policy.js +415 -0
- package/dist/cloud/services/scaling-policy.js.map +1 -0
- package/dist/cloud/vault/index.d.ts +76 -0
- package/dist/cloud/vault/index.d.ts.map +1 -0
- package/dist/cloud/vault/index.js +219 -0
- package/dist/cloud/vault/index.js.map +1 -0
- package/dist/cloud/webhooks/index.d.ts +24 -0
- package/dist/cloud/webhooks/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/index.js +29 -0
- package/dist/cloud/webhooks/index.js.map +1 -0
- package/dist/cloud/webhooks/parsers/github.d.ts +8 -0
- package/dist/cloud/webhooks/parsers/github.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/github.js +234 -0
- package/dist/cloud/webhooks/parsers/github.js.map +1 -0
- package/dist/cloud/webhooks/parsers/index.d.ts +23 -0
- package/dist/cloud/webhooks/parsers/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/index.js +30 -0
- package/dist/cloud/webhooks/parsers/index.js.map +1 -0
- package/dist/cloud/webhooks/parsers/linear.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/linear.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/linear.js +258 -0
- package/dist/cloud/webhooks/parsers/linear.js.map +1 -0
- package/dist/cloud/webhooks/parsers/slack.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/slack.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/slack.js +214 -0
- package/dist/cloud/webhooks/parsers/slack.js.map +1 -0
- package/dist/cloud/webhooks/responders/github.d.ts +8 -0
- package/dist/cloud/webhooks/responders/github.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/github.js +73 -0
- package/dist/cloud/webhooks/responders/github.js.map +1 -0
- package/dist/cloud/webhooks/responders/index.d.ts +23 -0
- package/dist/cloud/webhooks/responders/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/index.js +30 -0
- package/dist/cloud/webhooks/responders/index.js.map +1 -0
- package/dist/cloud/webhooks/responders/linear.d.ts +9 -0
- package/dist/cloud/webhooks/responders/linear.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/linear.js +149 -0
- package/dist/cloud/webhooks/responders/linear.js.map +1 -0
- package/dist/cloud/webhooks/responders/slack.d.ts +20 -0
- package/dist/cloud/webhooks/responders/slack.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/slack.js +178 -0
- package/dist/cloud/webhooks/responders/slack.js.map +1 -0
- package/dist/cloud/webhooks/router.d.ts +25 -0
- package/dist/cloud/webhooks/router.d.ts.map +1 -0
- package/dist/cloud/webhooks/router.js +504 -0
- package/dist/cloud/webhooks/router.js.map +1 -0
- package/dist/cloud/webhooks/rules-engine.d.ts +24 -0
- package/dist/cloud/webhooks/rules-engine.d.ts.map +1 -0
- package/dist/cloud/webhooks/rules-engine.js +287 -0
- package/dist/cloud/webhooks/rules-engine.js.map +1 -0
- package/dist/cloud/webhooks/types.d.ts +186 -0
- package/dist/cloud/webhooks/types.d.ts.map +1 -0
- package/dist/cloud/webhooks/types.js +8 -0
- package/dist/cloud/webhooks/types.js.map +1 -0
- package/dist/continuity/formatter.d.ts +51 -0
- package/dist/continuity/formatter.d.ts.map +1 -0
- package/dist/continuity/formatter.js +313 -0
- package/dist/continuity/formatter.js.map +1 -0
- package/dist/continuity/handoff-store.d.ts +67 -0
- package/dist/continuity/handoff-store.d.ts.map +1 -0
- package/dist/continuity/handoff-store.js +472 -0
- package/dist/continuity/handoff-store.js.map +1 -0
- package/dist/continuity/index.d.ts +45 -0
- package/dist/continuity/index.d.ts.map +1 -0
- package/dist/continuity/index.js +48 -0
- package/dist/continuity/index.js.map +1 -0
- package/dist/continuity/ledger-store.d.ts +110 -0
- package/dist/continuity/ledger-store.d.ts.map +1 -0
- package/dist/continuity/ledger-store.js +500 -0
- package/dist/continuity/ledger-store.js.map +1 -0
- package/dist/continuity/manager.d.ts +178 -0
- package/dist/continuity/manager.d.ts.map +1 -0
- package/dist/continuity/manager.js +562 -0
- package/dist/continuity/manager.js.map +1 -0
- package/dist/continuity/parser.d.ts +76 -0
- package/dist/continuity/parser.d.ts.map +1 -0
- package/dist/continuity/parser.js +579 -0
- package/dist/continuity/parser.js.map +1 -0
- package/dist/continuity/types.d.ts +180 -0
- package/dist/continuity/types.d.ts.map +1 -0
- package/dist/continuity/types.js +9 -0
- package/dist/continuity/types.js.map +1 -0
- package/dist/daemon/agent-manager.d.ts +114 -0
- package/dist/daemon/agent-manager.d.ts.map +1 -0
- package/dist/daemon/agent-manager.js +513 -0
- package/dist/daemon/agent-manager.js.map +1 -0
- package/dist/daemon/agent-registry.d.ts +34 -0
- package/dist/daemon/agent-registry.d.ts.map +1 -1
- package/dist/daemon/agent-registry.js +45 -2
- package/dist/daemon/agent-registry.js.map +1 -1
- package/dist/daemon/api.d.ts +81 -0
- package/dist/daemon/api.d.ts.map +1 -0
- package/dist/daemon/api.js +554 -0
- package/dist/daemon/api.js.map +1 -0
- package/dist/daemon/cli-auth.d.ts +67 -0
- package/dist/daemon/cli-auth.d.ts.map +1 -0
- package/dist/daemon/cli-auth.js +537 -0
- package/dist/daemon/cli-auth.js.map +1 -0
- package/dist/daemon/cloud-sync.d.ts +101 -0
- package/dist/daemon/cloud-sync.d.ts.map +1 -0
- package/dist/daemon/cloud-sync.js +263 -0
- package/dist/daemon/cloud-sync.js.map +1 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +6 -0
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/orchestrator.d.ts +155 -0
- package/dist/daemon/orchestrator.d.ts.map +1 -0
- package/dist/daemon/orchestrator.js +766 -0
- package/dist/daemon/orchestrator.js.map +1 -0
- package/dist/daemon/router.d.ts +29 -0
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +143 -21
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +42 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +199 -16
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/services/browser-testing.d.ts +88 -0
- package/dist/daemon/services/browser-testing.d.ts.map +1 -0
- package/dist/daemon/services/browser-testing.js +244 -0
- package/dist/daemon/services/browser-testing.js.map +1 -0
- package/dist/daemon/services/container-spawner.d.ts +135 -0
- package/dist/daemon/services/container-spawner.d.ts.map +1 -0
- package/dist/daemon/services/container-spawner.js +313 -0
- package/dist/daemon/services/container-spawner.js.map +1 -0
- package/dist/daemon/types.d.ts +131 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +6 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/daemon/workspace-manager.d.ts +75 -0
- package/dist/daemon/workspace-manager.d.ts.map +1 -0
- package/dist/daemon/workspace-manager.js +289 -0
- package/dist/daemon/workspace-manager.js.map +1 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/480-2d4111711d4e473c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/724-73c1ee5f60abe860.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/766-c3a14283c88d815b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-7120be68bea622f3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-dc2e3a1a22478efc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/page-56a8b4616a90dc43.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-3eac37ea6f5dd153.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-1081dd190a331a91.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-daf87e86f783f980.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-4d72d5a5d8a9b618.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-b68a681526eb145e.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-fee4ed1709070bcd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/{main-e0a1f53fe0617a63.js → main-97850e03d723ea8c.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
- package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +1 -0
- package/dist/dashboard/out/_next/static/css/411ce23ffeae9f76.css +1 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
- package/dist/dashboard/out/alt-logos/logo.svg +38 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
- package/dist/dashboard/out/app.html +1 -0
- package/dist/dashboard/out/app.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -0
- package/dist/dashboard/out/connect-repos.txt +7 -0
- package/dist/dashboard/out/history.html +1 -0
- package/dist/dashboard/out/history.txt +7 -0
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +6 -0
- package/dist/dashboard/out/login.txt +7 -0
- package/dist/dashboard/out/metrics.html +1 -515
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +13 -0
- package/dist/dashboard/out/pricing.txt +7 -0
- package/dist/dashboard/out/providers.html +1 -0
- package/dist/dashboard/out/providers.txt +7 -0
- package/dist/dashboard/out/signup.html +6 -0
- package/dist/dashboard/out/signup.txt +7 -0
- package/dist/dashboard-server/metrics.d.ts.map +1 -1
- package/dist/dashboard-server/metrics.js +3 -2
- package/dist/dashboard-server/metrics.js.map +1 -1
- package/dist/dashboard-server/server.d.ts.map +1 -1
- package/dist/dashboard-server/server.js +2653 -130
- package/dist/dashboard-server/server.js.map +1 -1
- package/dist/hooks/emitter.d.ts +40 -0
- package/dist/hooks/emitter.d.ts.map +1 -0
- package/dist/hooks/emitter.js +63 -0
- package/dist/hooks/emitter.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/registry.d.ts +173 -0
- package/dist/hooks/registry.d.ts.map +1 -0
- package/dist/hooks/registry.js +476 -0
- package/dist/hooks/registry.js.map +1 -0
- package/dist/hooks/trajectory-hooks.d.ts +52 -0
- package/dist/hooks/trajectory-hooks.d.ts.map +1 -0
- package/dist/hooks/trajectory-hooks.js +183 -0
- package/dist/hooks/trajectory-hooks.js.map +1 -0
- package/dist/hooks/types.d.ts +141 -0
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/adapters/index.d.ts +8 -0
- package/dist/memory/adapters/index.d.ts.map +1 -0
- package/dist/memory/adapters/index.js +8 -0
- package/dist/memory/adapters/index.js.map +1 -0
- package/dist/memory/adapters/inmemory.d.ts +59 -0
- package/dist/memory/adapters/inmemory.d.ts.map +1 -0
- package/dist/memory/adapters/inmemory.js +195 -0
- package/dist/memory/adapters/inmemory.js.map +1 -0
- package/dist/memory/adapters/supermemory.d.ts +71 -0
- package/dist/memory/adapters/supermemory.d.ts.map +1 -0
- package/dist/memory/adapters/supermemory.js +338 -0
- package/dist/memory/adapters/supermemory.js.map +1 -0
- package/dist/memory/factory.d.ts +48 -0
- package/dist/memory/factory.d.ts.map +1 -0
- package/dist/memory/factory.js +143 -0
- package/dist/memory/factory.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +32 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory-hooks.d.ts +60 -0
- package/dist/memory/memory-hooks.d.ts.map +1 -0
- package/dist/memory/memory-hooks.js +313 -0
- package/dist/memory/memory-hooks.js.map +1 -0
- package/dist/memory/service.d.ts +49 -0
- package/dist/memory/service.d.ts.map +1 -0
- package/dist/memory/service.js +146 -0
- package/dist/memory/service.js.map +1 -0
- package/dist/memory/types.d.ts +195 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +8 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/policy/agent-policy.d.ts +225 -0
- package/dist/policy/agent-policy.d.ts.map +1 -0
- package/dist/policy/agent-policy.js +665 -0
- package/dist/policy/agent-policy.js.map +1 -0
- package/dist/policy/cloud-policy-fetcher.d.ts +12 -0
- package/dist/policy/cloud-policy-fetcher.d.ts.map +1 -0
- package/dist/policy/cloud-policy-fetcher.js +64 -0
- package/dist/policy/cloud-policy-fetcher.js.map +1 -0
- package/dist/protocol/types.d.ts +10 -1
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/resiliency/context-persistence.d.ts +140 -0
- package/dist/resiliency/context-persistence.d.ts.map +1 -0
- package/dist/resiliency/context-persistence.js +397 -0
- package/dist/resiliency/context-persistence.js.map +1 -0
- package/dist/resiliency/crash-insights.d.ts +156 -0
- package/dist/resiliency/crash-insights.d.ts.map +1 -0
- package/dist/resiliency/crash-insights.js +492 -0
- package/dist/resiliency/crash-insights.js.map +1 -0
- package/dist/resiliency/gossip-health.d.ts +137 -0
- package/dist/resiliency/gossip-health.d.ts.map +1 -0
- package/dist/resiliency/gossip-health.js +241 -0
- package/dist/resiliency/gossip-health.js.map +1 -0
- package/dist/resiliency/health-monitor.d.ts +97 -0
- package/dist/resiliency/health-monitor.d.ts.map +1 -0
- package/dist/resiliency/health-monitor.js +291 -0
- package/dist/resiliency/health-monitor.js.map +1 -0
- package/dist/resiliency/index.d.ts +68 -0
- package/dist/resiliency/index.d.ts.map +1 -0
- package/dist/resiliency/index.js +68 -0
- package/dist/resiliency/index.js.map +1 -0
- package/dist/resiliency/leader-watchdog.d.ts +109 -0
- package/dist/resiliency/leader-watchdog.d.ts.map +1 -0
- package/dist/resiliency/leader-watchdog.js +189 -0
- package/dist/resiliency/leader-watchdog.js.map +1 -0
- package/dist/resiliency/logger.d.ts +114 -0
- package/dist/resiliency/logger.d.ts.map +1 -0
- package/dist/resiliency/logger.js +250 -0
- package/dist/resiliency/logger.js.map +1 -0
- package/dist/resiliency/memory-monitor.d.ts +172 -0
- package/dist/resiliency/memory-monitor.d.ts.map +1 -0
- package/dist/resiliency/memory-monitor.js +593 -0
- package/dist/resiliency/memory-monitor.js.map +1 -0
- package/dist/resiliency/metrics.d.ts +115 -0
- package/dist/resiliency/metrics.d.ts.map +1 -0
- package/dist/resiliency/metrics.js +239 -0
- package/dist/resiliency/metrics.js.map +1 -0
- package/dist/resiliency/provider-context.d.ts +100 -0
- package/dist/resiliency/provider-context.d.ts.map +1 -0
- package/dist/resiliency/provider-context.js +360 -0
- package/dist/resiliency/provider-context.js.map +1 -0
- package/dist/resiliency/stateless-lead.d.ts +149 -0
- package/dist/resiliency/stateless-lead.d.ts.map +1 -0
- package/dist/resiliency/stateless-lead.js +308 -0
- package/dist/resiliency/stateless-lead.js.map +1 -0
- package/dist/resiliency/supervisor.d.ts +147 -0
- package/dist/resiliency/supervisor.d.ts.map +1 -0
- package/dist/resiliency/supervisor.js +459 -0
- package/dist/resiliency/supervisor.js.map +1 -0
- package/dist/shared/cli-auth-config.d.ts +91 -0
- package/dist/shared/cli-auth-config.d.ts.map +1 -0
- package/dist/shared/cli-auth-config.js +264 -0
- package/dist/shared/cli-auth-config.js.map +1 -0
- package/dist/storage/adapter.d.ts +3 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/adapter.js +12 -2
- package/dist/storage/adapter.js.map +1 -1
- package/dist/storage/sqlite-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-adapter.js +18 -14
- package/dist/storage/sqlite-adapter.js.map +1 -1
- package/dist/trajectory/config.d.ts +84 -0
- package/dist/trajectory/config.d.ts.map +1 -0
- package/dist/trajectory/config.js +163 -0
- package/dist/trajectory/config.js.map +1 -0
- package/dist/trajectory/index.d.ts +8 -0
- package/dist/trajectory/index.d.ts.map +1 -0
- package/dist/trajectory/index.js +8 -0
- package/dist/trajectory/index.js.map +1 -0
- package/dist/trajectory/integration.d.ts +292 -0
- package/dist/trajectory/integration.d.ts.map +1 -0
- package/dist/trajectory/integration.js +834 -0
- package/dist/trajectory/integration.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +84 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-namespace.d.ts +24 -0
- package/dist/utils/project-namespace.d.ts.map +1 -1
- package/dist/utils/project-namespace.js +84 -0
- package/dist/utils/project-namespace.js.map +1 -1
- package/dist/wrapper/client.d.ts +16 -1
- package/dist/wrapper/client.d.ts.map +1 -1
- package/dist/wrapper/client.js +32 -1
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/parser.d.ts +13 -0
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +217 -47
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/pty-wrapper.d.ts +219 -17
- package/dist/wrapper/pty-wrapper.d.ts.map +1 -1
- package/dist/wrapper/pty-wrapper.js +1050 -104
- package/dist/wrapper/pty-wrapper.js.map +1 -1
- package/dist/wrapper/shared.d.ts +165 -0
- package/dist/wrapper/shared.d.ts.map +1 -0
- package/dist/wrapper/shared.js +270 -0
- package/dist/wrapper/shared.js.map +1 -0
- package/dist/wrapper/tmux-wrapper.d.ts +78 -11
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +567 -106
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/CLOUD-ARCHITECTURE.md +804 -0
- package/docs/CLOUD-ONBOARDING-DESIGN.md +1983 -0
- package/docs/HOOKS_API.md +394 -0
- package/docs/WRAPPER_EVENTS.md +358 -0
- package/docs/agent-policy-snippet.md +40 -0
- package/docs/agent-relay-protocol.md +238 -0
- package/docs/agent-relay-snippet.md +115 -6
- package/docs/archive/EXECUTIVE_SUMMARY.md +358 -0
- package/docs/archive/ROADMAP.md +329 -0
- package/docs/archive/TESTING_PRESENCE_FEATURES.md +327 -0
- package/docs/competitive/GASTOWN.md +451 -0
- package/docs/{COMPETITIVE_ANALYSIS.md → competitive/OVERVIEW.md} +1 -0
- package/docs/competitive/README.md +34 -0
- package/docs/competitive/TMUX_ORCHESTRATOR.md +605 -0
- package/docs/dashboard.png +0 -0
- package/docs/design/ci-failure-webhooks.md +812 -0
- package/docs/design/comprehensive-integrations.md +238 -0
- package/docs/design/e2b-sandbox-integration.md +504 -0
- package/docs/design/github-app-permissions.md +264 -0
- package/docs/guides/CLOUD.md +236 -0
- package/docs/guides/LOCAL.md +535 -0
- package/docs/guides/SELF-HOSTED.md +494 -0
- package/docs/local-testing.md +428 -0
- package/docs/proposals/continuous-claude-integration.md +622 -0
- package/docs/proposals/custom-commands.md +368 -0
- package/docs/proposals/shadow-as-subagent.md +765 -0
- package/docs/proposals/slack-bot-integration.md +1457 -0
- package/docs/tasks/global-skills-system.tasks.md +230 -0
- package/docs/tasks/webhook-integrations.tasks.md +184 -0
- package/docs/tasks/workspace-capabilities.tasks.md +121 -0
- package/docs/testing/RESILIENCY-TEST-PLAN-2026-01-01.md +366 -0
- package/package.json +45 -7
- package/scripts/cloud-setup.sh +96 -0
- package/scripts/manual-qa.sh +293 -0
- package/scripts/postinstall.js +60 -0
- package/scripts/run-cloud-qa.sh +220 -0
- package/scripts/test-cli-auth/Dockerfile +44 -0
- package/scripts/test-cli-auth/Dockerfile.real +79 -0
- package/scripts/test-cli-auth/README.md +286 -0
- package/scripts/test-cli-auth/ci-test-real-clis.ts +251 -0
- package/scripts/test-cli-auth/ci-test-runner.ts +263 -0
- package/scripts/test-cli-auth/mock-cli.sh +147 -0
- package/scripts/test-cli-auth/package.json +14 -0
- package/scripts/test-cli-auth/test-oauth-flow.ts +220 -0
- package/scripts/test-pty-input-auto.js +222 -0
- package/scripts/test-pty-input.js +150 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-c9d8c5d95e48c6bf.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-8aa9936bc6c771ab.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4498be09a5157759.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-bae2e535de00de50.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-c81f7fd28659d64f.js +0 -1
- package/dist/dashboard/out/_next/static/css/50ed6996e3df7bdd.css +0 -1
- /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_ssgManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{117-3bef7b19f3e60751.js → 117-b100311aff8d5c61.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{648-6cf686106c891ad3.js → 648-a13d3c2b1be45466.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/app/_not-found/{page-8ff6572bc7c9bc61.js → page-a4973f3e3c82fb67.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{fd9d1056-26bd8d656b496dba.js → fd9d1056-bf46c09eb57e019c.js} +0 -0
- /package/docs/{CHANGELOG.md → archive/CHANGELOG.md} +0 -0
- /package/docs/{CLI-SIMPLIFICATION-COMPLETE.md → archive/CLI-SIMPLIFICATION-COMPLETE.md} +0 -0
- /package/docs/{DESIGN_BRIDGE_STAFFING.md → archive/DESIGN_BRIDGE_STAFFING.md} +0 -0
- /package/docs/{DESIGN_V2.md → archive/DESIGN_V2.md} +0 -0
- /package/docs/{MONETIZATION.md → archive/MONETIZATION.md} +0 -0
- /package/docs/{PROPOSAL-trajectories.md → archive/PROPOSAL-trajectories.md} +0 -0
- /package/docs/{SCALING_ANALYSIS.md → archive/SCALING_ANALYSIS.md} +0 -0
- /package/docs/{TMUX_IMPLEMENTATION_NOTES.md → archive/TMUX_IMPLEMENTATION_NOTES.md} +0 -0
- /package/docs/{TMUX_IMPROVEMENTS.md → archive/TMUX_IMPROVEMENTS.md} +0 -0
- /package/docs/{dashboard-v2-plan.md → archive/dashboard-v2-plan.md} +0 -0
- /package/docs/{removable-code-analysis.md → archive/removable-code-analysis.md} +0 -0
- /package/docs/{competitive-analysis-mcp-agent-mail.md → competitive/MCP_AGENT_MAIL.md} +0 -0
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Relay Cloud - Workspace Provisioner
|
|
3
|
+
*
|
|
4
|
+
* One-click provisioning for compute resources (Fly.io, Railway, Docker).
|
|
5
|
+
*/
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
import { getConfig } from '../config.js';
|
|
8
|
+
import { db } from '../db/index.js';
|
|
9
|
+
import { vault } from '../vault/index.js';
|
|
10
|
+
import { nangoService } from '../services/nango.js';
|
|
11
|
+
const WORKSPACE_PORT = 3888;
|
|
12
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
13
|
+
const WORKSPACE_IMAGE = process.env.WORKSPACE_IMAGE || 'ghcr.io/agentworkforce/relay-workspace:latest';
|
|
14
|
+
/**
|
|
15
|
+
* Get a fresh GitHub App installation token from Nango.
|
|
16
|
+
* Looks up the user's connected repositories to find a valid Nango connection.
|
|
17
|
+
*/
|
|
18
|
+
async function getGithubAppTokenForUser(userId) {
|
|
19
|
+
try {
|
|
20
|
+
// Find any repository with a Nango connection for this user
|
|
21
|
+
const repos = await db.repositories.findByUserId(userId);
|
|
22
|
+
const repoWithConnection = repos.find(r => r.nangoConnectionId);
|
|
23
|
+
if (!repoWithConnection?.nangoConnectionId) {
|
|
24
|
+
console.warn(`[provisioner] No Nango GitHub App connection found for user ${userId}`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
// Get fresh installation token from Nango (handles refresh automatically)
|
|
28
|
+
const token = await nangoService.getGithubAppToken(repoWithConnection.nangoConnectionId);
|
|
29
|
+
return token;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`[provisioner] Failed to get GitHub App token for user ${userId}:`, error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function loadCredentialToken(userId, provider) {
|
|
37
|
+
try {
|
|
38
|
+
const cred = await vault.getCredential(userId, provider);
|
|
39
|
+
if (cred?.accessToken) {
|
|
40
|
+
return cred.accessToken;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.warn(`Failed to decrypt ${provider} credential from vault; trying raw storage fallback`, error);
|
|
45
|
+
const raw = await db.credentials.findByUserAndProvider(userId, provider);
|
|
46
|
+
return raw?.accessToken ?? null;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
async function wait(ms) {
|
|
51
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
async function fetchWithRetry(url, options = {}) {
|
|
54
|
+
const retries = options.retries ?? 2;
|
|
55
|
+
let attempt = 0;
|
|
56
|
+
while (attempt <= retries) {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(url, { ...options, signal: controller.signal });
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (!response.ok && response.status >= 500 && attempt < retries) {
|
|
63
|
+
attempt += 1;
|
|
64
|
+
await wait(500 * attempt);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
if (attempt >= retries) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
attempt += 1;
|
|
75
|
+
await wait(500 * attempt);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error('fetchWithRetry exhausted retries');
|
|
79
|
+
}
|
|
80
|
+
async function softHealthCheck(url) {
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetchWithRetry(`${url.replace(/\/$/, '')}/health`, { method: 'GET', retries: 1 });
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
console.warn(`[health] Non-200 from ${url}/health: ${res.status}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.warn(`[health] Failed to reach ${url}/health`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export const RESOURCE_TIERS = {
|
|
92
|
+
small: { name: 'small', cpuCores: 1, memoryMb: 512, maxAgents: 5 },
|
|
93
|
+
medium: { name: 'medium', cpuCores: 2, memoryMb: 1024, maxAgents: 10 },
|
|
94
|
+
large: { name: 'large', cpuCores: 4, memoryMb: 2048, maxAgents: 20 },
|
|
95
|
+
xlarge: { name: 'xlarge', cpuCores: 8, memoryMb: 4096, maxAgents: 50 },
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Fly.io provisioner
|
|
99
|
+
*/
|
|
100
|
+
class FlyProvisioner {
|
|
101
|
+
apiToken;
|
|
102
|
+
org;
|
|
103
|
+
region;
|
|
104
|
+
workspaceDomain;
|
|
105
|
+
cloudApiUrl;
|
|
106
|
+
sessionSecret;
|
|
107
|
+
registryAuth;
|
|
108
|
+
constructor() {
|
|
109
|
+
const config = getConfig();
|
|
110
|
+
if (!config.compute.fly) {
|
|
111
|
+
throw new Error('Fly.io configuration missing');
|
|
112
|
+
}
|
|
113
|
+
this.apiToken = config.compute.fly.apiToken;
|
|
114
|
+
this.org = config.compute.fly.org;
|
|
115
|
+
this.region = config.compute.fly.region || 'sjc';
|
|
116
|
+
this.workspaceDomain = config.compute.fly.workspaceDomain;
|
|
117
|
+
this.registryAuth = config.compute.fly.registryAuth;
|
|
118
|
+
this.cloudApiUrl = config.publicUrl;
|
|
119
|
+
this.sessionSecret = config.sessionSecret;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Generate a workspace token for API authentication
|
|
123
|
+
* This is a simple HMAC - in production, consider using JWTs
|
|
124
|
+
*/
|
|
125
|
+
generateWorkspaceToken(workspaceId) {
|
|
126
|
+
return crypto
|
|
127
|
+
.createHmac('sha256', this.sessionSecret)
|
|
128
|
+
.update(`workspace:${workspaceId}`)
|
|
129
|
+
.digest('hex');
|
|
130
|
+
}
|
|
131
|
+
async provision(workspace, credentials) {
|
|
132
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
133
|
+
// Create Fly app
|
|
134
|
+
await fetchWithRetry('https://api.machines.dev/v1/apps', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
138
|
+
'Content-Type': 'application/json',
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
app_name: appName,
|
|
142
|
+
org_slug: this.org,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
// Set secrets (provider credentials)
|
|
146
|
+
const secrets = {};
|
|
147
|
+
for (const [provider, token] of credentials) {
|
|
148
|
+
secrets[`${provider.toUpperCase()}_TOKEN`] = token;
|
|
149
|
+
}
|
|
150
|
+
if (Object.keys(secrets).length > 0) {
|
|
151
|
+
await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/secrets`, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: {
|
|
154
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(secrets),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// If custom workspace domain is configured, add certificate
|
|
161
|
+
const customHostname = this.workspaceDomain
|
|
162
|
+
? `${appName}.${this.workspaceDomain}`
|
|
163
|
+
: null;
|
|
164
|
+
if (customHostname) {
|
|
165
|
+
await this.allocateCertificate(appName, customHostname);
|
|
166
|
+
}
|
|
167
|
+
// Create machine with auto-stop/start for cost optimization
|
|
168
|
+
const machineResponse = await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
region: this.region,
|
|
176
|
+
config: {
|
|
177
|
+
image: WORKSPACE_IMAGE,
|
|
178
|
+
// Registry auth for private ghcr.io images
|
|
179
|
+
...(this.registryAuth && {
|
|
180
|
+
image_registry_auth: {
|
|
181
|
+
registry: 'ghcr.io',
|
|
182
|
+
username: this.registryAuth.username,
|
|
183
|
+
password: this.registryAuth.password,
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
env: {
|
|
187
|
+
WORKSPACE_ID: workspace.id,
|
|
188
|
+
SUPERVISOR_ENABLED: String(workspace.config.supervisorEnabled ?? false),
|
|
189
|
+
MAX_AGENTS: String(workspace.config.maxAgents ?? 10),
|
|
190
|
+
REPOSITORIES: (workspace.config.repositories ?? []).join(','),
|
|
191
|
+
PROVIDERS: (workspace.config.providers ?? []).join(','),
|
|
192
|
+
PORT: String(WORKSPACE_PORT),
|
|
193
|
+
AGENT_RELAY_DASHBOARD_PORT: String(WORKSPACE_PORT),
|
|
194
|
+
// Git gateway configuration
|
|
195
|
+
CLOUD_API_URL: this.cloudApiUrl,
|
|
196
|
+
WORKSPACE_TOKEN: this.generateWorkspaceToken(workspace.id),
|
|
197
|
+
},
|
|
198
|
+
services: [
|
|
199
|
+
{
|
|
200
|
+
ports: [
|
|
201
|
+
{ port: 443, handlers: ['tls', 'http'] },
|
|
202
|
+
{ port: 80, handlers: ['http'] },
|
|
203
|
+
],
|
|
204
|
+
protocol: 'tcp',
|
|
205
|
+
internal_port: WORKSPACE_PORT,
|
|
206
|
+
// Auto-stop after 5 minutes of inactivity
|
|
207
|
+
auto_stop_machines: true,
|
|
208
|
+
auto_start_machines: true,
|
|
209
|
+
min_machines_running: 0,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
guest: {
|
|
213
|
+
cpu_kind: 'shared',
|
|
214
|
+
cpus: 1,
|
|
215
|
+
memory_mb: 512,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
if (!machineResponse.ok) {
|
|
221
|
+
const error = await machineResponse.text();
|
|
222
|
+
throw new Error(`Failed to create Fly machine: ${error}`);
|
|
223
|
+
}
|
|
224
|
+
const machine = (await machineResponse.json());
|
|
225
|
+
// Return custom domain URL if configured, otherwise default fly.dev
|
|
226
|
+
const publicUrl = customHostname
|
|
227
|
+
? `https://${customHostname}`
|
|
228
|
+
: `https://${appName}.fly.dev`;
|
|
229
|
+
await softHealthCheck(publicUrl);
|
|
230
|
+
return {
|
|
231
|
+
computeId: machine.id,
|
|
232
|
+
publicUrl,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Allocate SSL certificate for custom domain
|
|
237
|
+
*/
|
|
238
|
+
async allocateCertificate(appName, hostname) {
|
|
239
|
+
const response = await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/certificates`, {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: {
|
|
242
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
},
|
|
245
|
+
body: JSON.stringify({ hostname }),
|
|
246
|
+
});
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
const error = await response.text();
|
|
249
|
+
// Don't fail if cert already exists
|
|
250
|
+
if (!error.includes('already exists')) {
|
|
251
|
+
throw new Error(`Failed to allocate certificate for ${hostname}: ${error}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async deprovision(workspace) {
|
|
256
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
257
|
+
await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}`, {
|
|
258
|
+
method: 'DELETE',
|
|
259
|
+
headers: {
|
|
260
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async getStatus(workspace) {
|
|
265
|
+
if (!workspace.computeId)
|
|
266
|
+
return 'error';
|
|
267
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
268
|
+
const response = await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines/${workspace.computeId}`, {
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok)
|
|
274
|
+
return 'error';
|
|
275
|
+
const machine = await response.json();
|
|
276
|
+
switch (machine.state) {
|
|
277
|
+
case 'started':
|
|
278
|
+
return 'running';
|
|
279
|
+
case 'stopped':
|
|
280
|
+
return 'stopped';
|
|
281
|
+
case 'created':
|
|
282
|
+
case 'starting':
|
|
283
|
+
return 'provisioning';
|
|
284
|
+
default:
|
|
285
|
+
return 'error';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async restart(workspace) {
|
|
289
|
+
if (!workspace.computeId)
|
|
290
|
+
return;
|
|
291
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
292
|
+
await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines/${workspace.computeId}/restart`, {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Resize workspace - vertical scaling via Fly Machines API
|
|
301
|
+
*/
|
|
302
|
+
async resize(workspace, tier) {
|
|
303
|
+
if (!workspace.computeId)
|
|
304
|
+
return;
|
|
305
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
306
|
+
// Update machine configuration
|
|
307
|
+
await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines/${workspace.computeId}`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: {
|
|
310
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
311
|
+
'Content-Type': 'application/json',
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify({
|
|
314
|
+
config: {
|
|
315
|
+
guest: {
|
|
316
|
+
cpu_kind: tier.cpuCores <= 2 ? 'shared' : 'performance',
|
|
317
|
+
cpus: tier.cpuCores,
|
|
318
|
+
memory_mb: tier.memoryMb,
|
|
319
|
+
},
|
|
320
|
+
env: {
|
|
321
|
+
MAX_AGENTS: String(tier.maxAgents),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
}),
|
|
325
|
+
});
|
|
326
|
+
console.log(`[fly] Resized workspace ${workspace.id} to ${tier.name} (${tier.cpuCores} CPU, ${tier.memoryMb}MB RAM)`);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Update the max agent limit for a workspace
|
|
330
|
+
*/
|
|
331
|
+
async updateAgentLimit(workspace, newLimit) {
|
|
332
|
+
if (!workspace.computeId)
|
|
333
|
+
return;
|
|
334
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
335
|
+
// Update environment variable
|
|
336
|
+
await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines/${workspace.computeId}`, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
headers: {
|
|
339
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
340
|
+
'Content-Type': 'application/json',
|
|
341
|
+
},
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
config: {
|
|
344
|
+
env: {
|
|
345
|
+
MAX_AGENTS: String(newLimit),
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
349
|
+
});
|
|
350
|
+
console.log(`[fly] Updated workspace ${workspace.id} agent limit to ${newLimit}`);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get current resource tier for a workspace
|
|
354
|
+
*/
|
|
355
|
+
async getCurrentTier(workspace) {
|
|
356
|
+
if (!workspace.computeId) {
|
|
357
|
+
return RESOURCE_TIERS.small;
|
|
358
|
+
}
|
|
359
|
+
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
360
|
+
const response = await fetchWithRetry(`https://api.machines.dev/v1/apps/${appName}/machines/${workspace.computeId}`, {
|
|
361
|
+
headers: {
|
|
362
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
return RESOURCE_TIERS.small;
|
|
367
|
+
}
|
|
368
|
+
const machine = await response.json();
|
|
369
|
+
const _cpus = machine.config?.guest?.cpus || 1;
|
|
370
|
+
const memoryMb = machine.config?.guest?.memory_mb || 512;
|
|
371
|
+
// Map to nearest tier
|
|
372
|
+
if (memoryMb >= 4096)
|
|
373
|
+
return RESOURCE_TIERS.xlarge;
|
|
374
|
+
if (memoryMb >= 2048)
|
|
375
|
+
return RESOURCE_TIERS.large;
|
|
376
|
+
if (memoryMb >= 1024)
|
|
377
|
+
return RESOURCE_TIERS.medium;
|
|
378
|
+
return RESOURCE_TIERS.small;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Railway provisioner
|
|
383
|
+
*/
|
|
384
|
+
class RailwayProvisioner {
|
|
385
|
+
apiToken;
|
|
386
|
+
cloudApiUrl;
|
|
387
|
+
sessionSecret;
|
|
388
|
+
constructor() {
|
|
389
|
+
const config = getConfig();
|
|
390
|
+
if (!config.compute.railway) {
|
|
391
|
+
throw new Error('Railway configuration missing');
|
|
392
|
+
}
|
|
393
|
+
this.apiToken = config.compute.railway.apiToken;
|
|
394
|
+
this.cloudApiUrl = config.publicUrl;
|
|
395
|
+
this.sessionSecret = config.sessionSecret;
|
|
396
|
+
}
|
|
397
|
+
generateWorkspaceToken(workspaceId) {
|
|
398
|
+
return crypto
|
|
399
|
+
.createHmac('sha256', this.sessionSecret)
|
|
400
|
+
.update(`workspace:${workspaceId}`)
|
|
401
|
+
.digest('hex');
|
|
402
|
+
}
|
|
403
|
+
async provision(workspace, credentials) {
|
|
404
|
+
// Create project
|
|
405
|
+
const projectResponse = await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
406
|
+
method: 'POST',
|
|
407
|
+
headers: {
|
|
408
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
409
|
+
'Content-Type': 'application/json',
|
|
410
|
+
},
|
|
411
|
+
body: JSON.stringify({
|
|
412
|
+
query: `
|
|
413
|
+
mutation CreateProject($input: ProjectCreateInput!) {
|
|
414
|
+
projectCreate(input: $input) {
|
|
415
|
+
id
|
|
416
|
+
name
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
`,
|
|
420
|
+
variables: {
|
|
421
|
+
input: {
|
|
422
|
+
name: `agent-relay-${workspace.id.substring(0, 8)}`,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
426
|
+
});
|
|
427
|
+
const projectData = await projectResponse.json();
|
|
428
|
+
const projectId = projectData.data.projectCreate.id;
|
|
429
|
+
// Deploy service
|
|
430
|
+
const serviceResponse = await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
431
|
+
method: 'POST',
|
|
432
|
+
headers: {
|
|
433
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
434
|
+
'Content-Type': 'application/json',
|
|
435
|
+
},
|
|
436
|
+
body: JSON.stringify({
|
|
437
|
+
query: `
|
|
438
|
+
mutation CreateService($input: ServiceCreateInput!) {
|
|
439
|
+
serviceCreate(input: $input) {
|
|
440
|
+
id
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
`,
|
|
444
|
+
variables: {
|
|
445
|
+
input: {
|
|
446
|
+
projectId,
|
|
447
|
+
name: 'workspace',
|
|
448
|
+
source: {
|
|
449
|
+
image: WORKSPACE_IMAGE,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
const serviceData = await serviceResponse.json();
|
|
456
|
+
const serviceId = serviceData.data.serviceCreate.id;
|
|
457
|
+
// Set environment variables
|
|
458
|
+
const envVars = {
|
|
459
|
+
WORKSPACE_ID: workspace.id,
|
|
460
|
+
SUPERVISOR_ENABLED: String(workspace.config.supervisorEnabled ?? false),
|
|
461
|
+
MAX_AGENTS: String(workspace.config.maxAgents ?? 10),
|
|
462
|
+
REPOSITORIES: (workspace.config.repositories ?? []).join(','),
|
|
463
|
+
PROVIDERS: (workspace.config.providers ?? []).join(','),
|
|
464
|
+
PORT: String(WORKSPACE_PORT),
|
|
465
|
+
AGENT_RELAY_DASHBOARD_PORT: String(WORKSPACE_PORT),
|
|
466
|
+
CLOUD_API_URL: this.cloudApiUrl,
|
|
467
|
+
WORKSPACE_TOKEN: this.generateWorkspaceToken(workspace.id),
|
|
468
|
+
};
|
|
469
|
+
for (const [provider, token] of credentials) {
|
|
470
|
+
envVars[`${provider.toUpperCase()}_TOKEN`] = token;
|
|
471
|
+
}
|
|
472
|
+
await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
headers: {
|
|
475
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
476
|
+
'Content-Type': 'application/json',
|
|
477
|
+
},
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
query: `
|
|
480
|
+
mutation SetVariables($input: VariableCollectionUpsertInput!) {
|
|
481
|
+
variableCollectionUpsert(input: $input)
|
|
482
|
+
}
|
|
483
|
+
`,
|
|
484
|
+
variables: {
|
|
485
|
+
input: {
|
|
486
|
+
projectId,
|
|
487
|
+
serviceId,
|
|
488
|
+
variables: envVars,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
}),
|
|
492
|
+
});
|
|
493
|
+
// Generate domain
|
|
494
|
+
const domainResponse = await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
498
|
+
'Content-Type': 'application/json',
|
|
499
|
+
},
|
|
500
|
+
body: JSON.stringify({
|
|
501
|
+
query: `
|
|
502
|
+
mutation CreateDomain($input: ServiceDomainCreateInput!) {
|
|
503
|
+
serviceDomainCreate(input: $input) {
|
|
504
|
+
domain
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
`,
|
|
508
|
+
variables: {
|
|
509
|
+
input: {
|
|
510
|
+
serviceId,
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
});
|
|
515
|
+
const domainData = await domainResponse.json();
|
|
516
|
+
const domain = domainData.data.serviceDomainCreate.domain;
|
|
517
|
+
await softHealthCheck(`https://${domain}`);
|
|
518
|
+
return {
|
|
519
|
+
computeId: projectId,
|
|
520
|
+
publicUrl: `https://${domain}`,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
async deprovision(workspace) {
|
|
524
|
+
if (!workspace.computeId)
|
|
525
|
+
return;
|
|
526
|
+
await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
527
|
+
method: 'POST',
|
|
528
|
+
headers: {
|
|
529
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
530
|
+
'Content-Type': 'application/json',
|
|
531
|
+
},
|
|
532
|
+
body: JSON.stringify({
|
|
533
|
+
query: `
|
|
534
|
+
mutation DeleteProject($id: String!) {
|
|
535
|
+
projectDelete(id: $id)
|
|
536
|
+
}
|
|
537
|
+
`,
|
|
538
|
+
variables: {
|
|
539
|
+
id: workspace.computeId,
|
|
540
|
+
},
|
|
541
|
+
}),
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
async getStatus(workspace) {
|
|
545
|
+
if (!workspace.computeId)
|
|
546
|
+
return 'error';
|
|
547
|
+
const response = await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
548
|
+
method: 'POST',
|
|
549
|
+
headers: {
|
|
550
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
551
|
+
'Content-Type': 'application/json',
|
|
552
|
+
},
|
|
553
|
+
body: JSON.stringify({
|
|
554
|
+
query: `
|
|
555
|
+
query GetProject($id: String!) {
|
|
556
|
+
project(id: $id) {
|
|
557
|
+
deployments {
|
|
558
|
+
edges {
|
|
559
|
+
node {
|
|
560
|
+
status
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
`,
|
|
567
|
+
variables: {
|
|
568
|
+
id: workspace.computeId,
|
|
569
|
+
},
|
|
570
|
+
}),
|
|
571
|
+
});
|
|
572
|
+
const data = await response.json();
|
|
573
|
+
const deployments = data.data?.project?.deployments?.edges;
|
|
574
|
+
if (!deployments || deployments.length === 0)
|
|
575
|
+
return 'provisioning';
|
|
576
|
+
const latestStatus = deployments[0].node.status;
|
|
577
|
+
switch (latestStatus) {
|
|
578
|
+
case 'SUCCESS':
|
|
579
|
+
return 'running';
|
|
580
|
+
case 'BUILDING':
|
|
581
|
+
case 'DEPLOYING':
|
|
582
|
+
return 'provisioning';
|
|
583
|
+
case 'CRASHED':
|
|
584
|
+
case 'FAILED':
|
|
585
|
+
return 'error';
|
|
586
|
+
default:
|
|
587
|
+
return 'stopped';
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async restart(workspace) {
|
|
591
|
+
// Railway doesn't have a direct restart - redeploy instead
|
|
592
|
+
if (!workspace.computeId)
|
|
593
|
+
return;
|
|
594
|
+
await fetchWithRetry('https://backboard.railway.app/graphql/v2', {
|
|
595
|
+
method: 'POST',
|
|
596
|
+
headers: {
|
|
597
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
598
|
+
'Content-Type': 'application/json',
|
|
599
|
+
},
|
|
600
|
+
body: JSON.stringify({
|
|
601
|
+
query: `
|
|
602
|
+
mutation RedeployService($input: DeploymentTriggerInput!) {
|
|
603
|
+
deploymentTrigger(input: $input)
|
|
604
|
+
}
|
|
605
|
+
`,
|
|
606
|
+
variables: {
|
|
607
|
+
input: {
|
|
608
|
+
projectId: workspace.computeId,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
}),
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Local Docker provisioner (for development/self-hosted)
|
|
617
|
+
*/
|
|
618
|
+
class DockerProvisioner {
|
|
619
|
+
cloudApiUrl;
|
|
620
|
+
cloudApiUrlForContainer;
|
|
621
|
+
sessionSecret;
|
|
622
|
+
constructor() {
|
|
623
|
+
const config = getConfig();
|
|
624
|
+
this.cloudApiUrl = config.publicUrl;
|
|
625
|
+
this.sessionSecret = config.sessionSecret;
|
|
626
|
+
// For Docker containers, localhost won't work - they need to reach the host
|
|
627
|
+
// Convert localhost URLs to host.docker.internal for container access
|
|
628
|
+
if (this.cloudApiUrl.includes('localhost') || this.cloudApiUrl.includes('127.0.0.1')) {
|
|
629
|
+
this.cloudApiUrlForContainer = this.cloudApiUrl
|
|
630
|
+
.replace('localhost', 'host.docker.internal')
|
|
631
|
+
.replace('127.0.0.1', 'host.docker.internal');
|
|
632
|
+
console.log(`[docker] Container API URL: ${this.cloudApiUrlForContainer} (host: ${this.cloudApiUrl})`);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
this.cloudApiUrlForContainer = this.cloudApiUrl;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
generateWorkspaceToken(workspaceId) {
|
|
639
|
+
return crypto
|
|
640
|
+
.createHmac('sha256', this.sessionSecret)
|
|
641
|
+
.update(`workspace:${workspaceId}`)
|
|
642
|
+
.digest('hex');
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Wait for container to be healthy by polling the health endpoint
|
|
646
|
+
*/
|
|
647
|
+
async waitForHealthy(publicUrl, timeoutMs = 60_000) {
|
|
648
|
+
const startTime = Date.now();
|
|
649
|
+
const pollInterval = 2000;
|
|
650
|
+
console.log(`[docker] Waiting for container to be healthy at ${publicUrl}...`);
|
|
651
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
652
|
+
try {
|
|
653
|
+
const response = await fetch(`${publicUrl}/health`, {
|
|
654
|
+
method: 'GET',
|
|
655
|
+
signal: AbortSignal.timeout(5000),
|
|
656
|
+
});
|
|
657
|
+
if (response.ok) {
|
|
658
|
+
console.log(`[docker] Container healthy after ${Date.now() - startTime}ms`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
// Container not ready yet, continue polling
|
|
664
|
+
}
|
|
665
|
+
await wait(pollInterval);
|
|
666
|
+
}
|
|
667
|
+
throw new Error(`Container did not become healthy within ${timeoutMs}ms`);
|
|
668
|
+
}
|
|
669
|
+
async provision(workspace, credentials) {
|
|
670
|
+
const containerName = `ar-${workspace.id.substring(0, 8)}`;
|
|
671
|
+
// Build environment variables
|
|
672
|
+
const envArgs = [
|
|
673
|
+
`-e WORKSPACE_ID=${workspace.id}`,
|
|
674
|
+
`-e SUPERVISOR_ENABLED=${workspace.config.supervisorEnabled ?? false}`,
|
|
675
|
+
`-e MAX_AGENTS=${workspace.config.maxAgents ?? 10}`,
|
|
676
|
+
`-e REPOSITORIES=${(workspace.config.repositories ?? []).join(',')}`,
|
|
677
|
+
`-e PROVIDERS=${(workspace.config.providers ?? []).join(',')}`,
|
|
678
|
+
`-e PORT=${WORKSPACE_PORT}`,
|
|
679
|
+
`-e AGENT_RELAY_DASHBOARD_PORT=${WORKSPACE_PORT}`,
|
|
680
|
+
`-e CLOUD_API_URL=${this.cloudApiUrlForContainer}`,
|
|
681
|
+
`-e WORKSPACE_TOKEN=${this.generateWorkspaceToken(workspace.id)}`,
|
|
682
|
+
];
|
|
683
|
+
for (const [provider, token] of credentials) {
|
|
684
|
+
envArgs.push(`-e ${provider.toUpperCase()}_TOKEN=${token}`);
|
|
685
|
+
}
|
|
686
|
+
// Run container
|
|
687
|
+
const { execSync } = await import('child_process');
|
|
688
|
+
const hostPort = 3000 + Math.floor(Math.random() * 1000);
|
|
689
|
+
// When running in Docker, connect to the same network for container-to-container communication
|
|
690
|
+
const runningInDocker = process.env.RUNNING_IN_DOCKER === 'true';
|
|
691
|
+
const networkArg = runningInDocker ? '--network agent-relay-dev' : '';
|
|
692
|
+
// In development, mount local dist and docs folders for faster iteration
|
|
693
|
+
// Set WORKSPACE_DEV_MOUNT=true to enable
|
|
694
|
+
const devMount = process.env.WORKSPACE_DEV_MOUNT === 'true';
|
|
695
|
+
const volumeArgs = devMount
|
|
696
|
+
? `-v "${process.cwd()}/dist:/app/dist:ro" -v "${process.cwd()}/docs:/app/docs:ro"`
|
|
697
|
+
: '';
|
|
698
|
+
if (devMount) {
|
|
699
|
+
console.log('[provisioner] Dev mode: mounting local dist/ and docs/ folders into workspace container');
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
execSync(`docker run -d --user root --name ${containerName} ${networkArg} ${volumeArgs} -p ${hostPort}:${WORKSPACE_PORT} ${envArgs.join(' ')} ${WORKSPACE_IMAGE}`, { stdio: 'pipe' });
|
|
703
|
+
const publicUrl = `http://localhost:${hostPort}`;
|
|
704
|
+
// Wait for container to be healthy before returning
|
|
705
|
+
// When running in Docker, use the internal container name for health check
|
|
706
|
+
const healthCheckUrl = runningInDocker
|
|
707
|
+
? `http://${containerName}:${WORKSPACE_PORT}`
|
|
708
|
+
: publicUrl;
|
|
709
|
+
await this.waitForHealthy(healthCheckUrl);
|
|
710
|
+
return {
|
|
711
|
+
computeId: containerName,
|
|
712
|
+
publicUrl,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
// Clean up container if it was created but health check failed
|
|
717
|
+
try {
|
|
718
|
+
const { execSync: execSyncCleanup } = await import('child_process');
|
|
719
|
+
execSyncCleanup(`docker rm -f ${containerName}`, { stdio: 'pipe' });
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
// Ignore cleanup errors
|
|
723
|
+
}
|
|
724
|
+
throw new Error(`Failed to start Docker container: ${error}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
async deprovision(workspace) {
|
|
728
|
+
if (!workspace.computeId)
|
|
729
|
+
return;
|
|
730
|
+
const { execSync } = await import('child_process');
|
|
731
|
+
try {
|
|
732
|
+
execSync(`docker rm -f ${workspace.computeId}`, { stdio: 'pipe' });
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
// Container may already be removed
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async getStatus(workspace) {
|
|
739
|
+
if (!workspace.computeId)
|
|
740
|
+
return 'error';
|
|
741
|
+
const { execSync } = await import('child_process');
|
|
742
|
+
try {
|
|
743
|
+
const result = execSync(`docker inspect -f '{{.State.Status}}' ${workspace.computeId}`, { stdio: 'pipe' }).toString().trim();
|
|
744
|
+
switch (result) {
|
|
745
|
+
case 'running':
|
|
746
|
+
return 'running';
|
|
747
|
+
case 'exited':
|
|
748
|
+
case 'dead':
|
|
749
|
+
return 'stopped';
|
|
750
|
+
case 'created':
|
|
751
|
+
case 'restarting':
|
|
752
|
+
return 'provisioning';
|
|
753
|
+
default:
|
|
754
|
+
return 'error';
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
return 'error';
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async restart(workspace) {
|
|
762
|
+
if (!workspace.computeId)
|
|
763
|
+
return;
|
|
764
|
+
const { execSync } = await import('child_process');
|
|
765
|
+
try {
|
|
766
|
+
execSync(`docker restart ${workspace.computeId}`, { stdio: 'pipe' });
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
throw new Error(`Failed to restart container: ${error}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Main Workspace Provisioner
|
|
775
|
+
*/
|
|
776
|
+
export class WorkspaceProvisioner {
|
|
777
|
+
provisioner;
|
|
778
|
+
constructor() {
|
|
779
|
+
const config = getConfig();
|
|
780
|
+
switch (config.compute.provider) {
|
|
781
|
+
case 'fly':
|
|
782
|
+
this.provisioner = new FlyProvisioner();
|
|
783
|
+
break;
|
|
784
|
+
case 'railway':
|
|
785
|
+
this.provisioner = new RailwayProvisioner();
|
|
786
|
+
break;
|
|
787
|
+
case 'docker':
|
|
788
|
+
default:
|
|
789
|
+
this.provisioner = new DockerProvisioner();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Provision a new workspace (one-click)
|
|
794
|
+
*/
|
|
795
|
+
async provision(config) {
|
|
796
|
+
// Create workspace record
|
|
797
|
+
const workspace = await db.workspaces.create({
|
|
798
|
+
userId: config.userId,
|
|
799
|
+
name: config.name,
|
|
800
|
+
computeProvider: getConfig().compute.provider,
|
|
801
|
+
config: {
|
|
802
|
+
providers: config.providers,
|
|
803
|
+
repositories: config.repositories,
|
|
804
|
+
supervisorEnabled: config.supervisorEnabled ?? true,
|
|
805
|
+
maxAgents: config.maxAgents ?? 10,
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
// Add creator as owner in workspace_members for team collaboration support
|
|
809
|
+
await db.workspaceMembers.addMember({
|
|
810
|
+
workspaceId: workspace.id,
|
|
811
|
+
userId: config.userId,
|
|
812
|
+
role: 'owner',
|
|
813
|
+
invitedBy: config.userId, // Self-invited as creator
|
|
814
|
+
});
|
|
815
|
+
// Auto-accept the creator's membership
|
|
816
|
+
await db.workspaceMembers.acceptInvite(workspace.id, config.userId);
|
|
817
|
+
// Get credentials
|
|
818
|
+
const credentials = new Map();
|
|
819
|
+
for (const provider of config.providers) {
|
|
820
|
+
const token = await loadCredentialToken(config.userId, provider);
|
|
821
|
+
if (token) {
|
|
822
|
+
credentials.set(provider, token);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// GitHub token is required for cloning repositories
|
|
826
|
+
// Use direct token if provided (for testing), otherwise get from Nango
|
|
827
|
+
if (config.repositories.length > 0) {
|
|
828
|
+
if (config.githubToken) {
|
|
829
|
+
// Direct token provided (for testing)
|
|
830
|
+
credentials.set('github', config.githubToken);
|
|
831
|
+
console.log('[provisioner] Using provided GitHub token');
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// Get fresh installation token from Nango GitHub App
|
|
835
|
+
const githubToken = await getGithubAppTokenForUser(config.userId);
|
|
836
|
+
if (githubToken) {
|
|
837
|
+
credentials.set('github', githubToken);
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
console.warn(`[provisioner] No GitHub App token for user ${config.userId}; repository cloning may fail.`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// Provision compute
|
|
845
|
+
try {
|
|
846
|
+
const { computeId, publicUrl } = await this.provisioner.provision(workspace, credentials);
|
|
847
|
+
await db.workspaces.updateStatus(workspace.id, 'running', {
|
|
848
|
+
computeId,
|
|
849
|
+
publicUrl,
|
|
850
|
+
});
|
|
851
|
+
return {
|
|
852
|
+
workspaceId: workspace.id,
|
|
853
|
+
status: 'running',
|
|
854
|
+
publicUrl,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
859
|
+
await db.workspaces.updateStatus(workspace.id, 'error', {
|
|
860
|
+
errorMessage,
|
|
861
|
+
});
|
|
862
|
+
return {
|
|
863
|
+
workspaceId: workspace.id,
|
|
864
|
+
status: 'error',
|
|
865
|
+
error: errorMessage,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Deprovision a workspace
|
|
871
|
+
*/
|
|
872
|
+
async deprovision(workspaceId) {
|
|
873
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
874
|
+
if (!workspace) {
|
|
875
|
+
throw new Error('Workspace not found');
|
|
876
|
+
}
|
|
877
|
+
await this.provisioner.deprovision(workspace);
|
|
878
|
+
await db.workspaces.delete(workspaceId);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Get workspace status
|
|
882
|
+
*/
|
|
883
|
+
async getStatus(workspaceId) {
|
|
884
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
885
|
+
if (!workspace) {
|
|
886
|
+
throw new Error('Workspace not found');
|
|
887
|
+
}
|
|
888
|
+
const status = await this.provisioner.getStatus(workspace);
|
|
889
|
+
// Update database if status changed
|
|
890
|
+
if (status !== workspace.status) {
|
|
891
|
+
await db.workspaces.updateStatus(workspaceId, status);
|
|
892
|
+
}
|
|
893
|
+
return status;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Restart a workspace
|
|
897
|
+
*/
|
|
898
|
+
async restart(workspaceId) {
|
|
899
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
900
|
+
if (!workspace) {
|
|
901
|
+
throw new Error('Workspace not found');
|
|
902
|
+
}
|
|
903
|
+
await this.provisioner.restart(workspace);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Stop a workspace
|
|
907
|
+
*/
|
|
908
|
+
async stop(workspaceId) {
|
|
909
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
910
|
+
if (!workspace) {
|
|
911
|
+
throw new Error('Workspace not found');
|
|
912
|
+
}
|
|
913
|
+
// For now, just deprovision to stop
|
|
914
|
+
await this.provisioner.deprovision(workspace);
|
|
915
|
+
await db.workspaces.updateStatus(workspaceId, 'stopped');
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Resize a workspace (vertical scaling)
|
|
919
|
+
*/
|
|
920
|
+
async resize(workspaceId, tier) {
|
|
921
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
922
|
+
if (!workspace) {
|
|
923
|
+
throw new Error('Workspace not found');
|
|
924
|
+
}
|
|
925
|
+
if (!this.provisioner.resize) {
|
|
926
|
+
throw new Error('Resize not supported by current compute provider');
|
|
927
|
+
}
|
|
928
|
+
await this.provisioner.resize(workspace, tier);
|
|
929
|
+
// Update workspace config with new limits
|
|
930
|
+
await db.workspaces.updateConfig(workspaceId, {
|
|
931
|
+
...workspace.config,
|
|
932
|
+
maxAgents: tier.maxAgents,
|
|
933
|
+
resourceTier: tier.name,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Update the max agent limit for a workspace
|
|
938
|
+
*/
|
|
939
|
+
async updateAgentLimit(workspaceId, newLimit) {
|
|
940
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
941
|
+
if (!workspace) {
|
|
942
|
+
throw new Error('Workspace not found');
|
|
943
|
+
}
|
|
944
|
+
if (this.provisioner.updateAgentLimit) {
|
|
945
|
+
await this.provisioner.updateAgentLimit(workspace, newLimit);
|
|
946
|
+
}
|
|
947
|
+
// Update workspace config
|
|
948
|
+
await db.workspaces.updateConfig(workspaceId, {
|
|
949
|
+
...workspace.config,
|
|
950
|
+
maxAgents: newLimit,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Get current resource tier for a workspace
|
|
955
|
+
*/
|
|
956
|
+
async getCurrentTier(workspaceId) {
|
|
957
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
958
|
+
if (!workspace) {
|
|
959
|
+
throw new Error('Workspace not found');
|
|
960
|
+
}
|
|
961
|
+
if (this.provisioner.getCurrentTier) {
|
|
962
|
+
return this.provisioner.getCurrentTier(workspace);
|
|
963
|
+
}
|
|
964
|
+
// Fallback: determine from config or default to small
|
|
965
|
+
const tierName = workspace.config.resourceTier || 'small';
|
|
966
|
+
return RESOURCE_TIERS[tierName] || RESOURCE_TIERS.small;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// Singleton instance
|
|
970
|
+
let _provisioner = null;
|
|
971
|
+
export function getProvisioner() {
|
|
972
|
+
if (!_provisioner) {
|
|
973
|
+
_provisioner = new WorkspaceProvisioner();
|
|
974
|
+
}
|
|
975
|
+
return _provisioner;
|
|
976
|
+
}
|
|
977
|
+
//# sourceMappingURL=index.js.map
|