@taskforcehq/taskforce 0.3.307 → 0.3.309

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.
Files changed (50) hide show
  1. package/dist/components/views/PlanComparisonPage.d.ts +20 -15
  2. package/dist/components/views/PlanComparisonPage.js +214 -83
  3. package/dist/components/views/PlanComparisonPage.test.js +249 -14
  4. package/dist/components/views/PlansPage.js +15 -9
  5. package/dist/components/views/PlansPage.test.js +4 -2
  6. package/dist/core/GlobalSettingsService.js +18 -0
  7. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  8. package/dist/core/GlobalSettingsService.test.js +45 -0
  9. package/dist/core/PlanEntitlementService.d.ts +20 -0
  10. package/dist/core/PlanEntitlementService.js +215 -19
  11. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  12. package/dist/core/Taskforce.d.ts +14 -0
  13. package/dist/core/Taskforce.js +6 -0
  14. package/dist/core/types.d.ts +3 -0
  15. package/dist/migrations/taskSchemaMigrations.js +37 -15
  16. package/dist/server/routes/admin.js +146 -5
  17. package/dist/server/routes/authSupport.d.ts +1 -0
  18. package/dist/server/routes/authSupport.js +2 -1
  19. package/dist/server/routes/billing.d.ts +1 -0
  20. package/dist/server/routes/billing.js +105 -63
  21. package/dist/server/routes/billing.test.js +159 -11
  22. package/dist/server/routes.test.js +91 -10
  23. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  24. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  25. package/dist/ui/agent-logos/chatgpt.png +0 -0
  26. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  27. package/dist/ui/agent-logos/codex.jpeg +0 -0
  28. package/dist/ui/agent-logos/cursor.png +0 -0
  29. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  30. package/dist/ui/agent-logos/windsurf.png +0 -0
  31. package/dist/ui/assets/{AgentsModule-2y82_3DO.js → AgentsModule-BS4Pi_nC.js} +1 -1
  32. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTLZZCiQ.js → AnnotatedAttachmentWorkspace-CCckzWYZ.js} +1 -1
  33. package/dist/ui/assets/{ContextAttachmentManager-D2epuNnG.js → ContextAttachmentManager-CfG_ER7C.js} +1 -1
  34. package/dist/ui/assets/{DocumentWorkspace-sVa-3Do6.js → DocumentWorkspace-BVaWPA25.js} +1 -1
  35. package/dist/ui/assets/{EntityActivityTimeline-Zods27y_.js → EntityActivityTimeline-DSj0ThaK.js} +1 -1
  36. package/dist/ui/assets/{InitiativesModule-D4XseTwW.js → InitiativesModule-AxMkWtxH.js} +1 -1
  37. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  38. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  39. package/dist/ui/assets/{TaskContextUpload-uC5qx4Bv.js → TaskContextUpload-Dw4rTF4i.js} +1 -1
  40. package/dist/ui/assets/{TaskSettings-CqvPAsTv.js → TaskSettings-f2ga42_V.js} +1 -1
  41. package/dist/ui/assets/{WorkflowsModule-oOA9bcdK.js → WorkflowsModule-E4UL3mov.js} +1 -1
  42. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  43. package/dist/ui/assets/{index-CYyodXsg.css → index-BvAbqx5Q.css} +1 -1
  44. package/dist/ui/assets/{index-607WPo_X.js → index-MAHKvFqp.js} +4 -4
  45. package/dist/ui/index.html +2 -2
  46. package/dist/ui/og-image.png +0 -0
  47. package/package.json +1 -1
  48. package/dist/ui/assets/PlansPage-D5AcbO0L.js +0 -1
  49. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +0 -1
  50. package/dist/ui/assets/documentReferences-DPZuTgHh.js +0 -1
@@ -72,6 +72,7 @@ import { buildCloudMcpRuntimeAuth, resolveErrorStatusCode } from './shared.js';
72
72
  import { buildAuthBehavior, buildSiteBehavior, getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl, } from './authSupport.js';
73
73
  import { applyWorkspaceRepair, scanWorkspaceRepair } from '../../sync/workspaceRepair.js';
74
74
  import { testMintAppleClientSecret } from '../auth/providers/appleClientSecret.js';
75
+ const PRICING_PAGE_DESCRIPTION_MAX_LENGTH = 2000;
75
76
  // === SECTION: Registration ===
76
77
  export function registerAdminRoutes(deps) {
77
78
  const { addRoute, core, context, requestWorkspaceId, requestUserId, requestRole, requestHeader, requestRuntimeMode, ensureAppAccess, ensureAdminRole, ensureCloudPlanData, requireCloudRuntime, resolveAuthenticatedUserId, resolveAdminRequestActor, auditAdminAction, resolveEffectiveObjectStorageConfig, getObjectStorageClient, taskAssetStore, normalizeDocumentPathInput, ensureWithinDir, getAssetMetadata, markAssetDeleted, clearAssetDeleted, removeAssetMetadata, getStorageTelemetry, appendStorageTelemetry, getStorageIntegrityHistory, appendStorageIntegrityHistory, getDocumentPurgeHistory, appendDocumentPurgeHistory, getCollaborationTelemetry, isCollaborationPolicyEnabled, normalizeMcpOauthScopeString, MCP_TOKEN_SCOPE_VALUES, MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS, MCP_WRITE_RATE_LIMIT_ENABLED, MCP_WRITE_RATE_LIMIT_ACTOR_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WORKSPACE_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WINDOW_MS, } = deps;
@@ -712,6 +713,88 @@ export function registerAdminRoutes(deps) {
712
713
  res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to list feature catalog'), code: error?.code }));
713
714
  }
714
715
  });
716
+ addRoute('POST', '/api/platform/features', async (req, res) => {
717
+ if (!ensureCloudPlanData(req, res))
718
+ return;
719
+ if (!ensureAdminRole(req)) {
720
+ res.writeHead(403, { 'Content-Type': 'application/json' });
721
+ res.end(JSON.stringify({ error: 'Forbidden: system admin role required.' }));
722
+ return;
723
+ }
724
+ const body = await parseJsonBody(req);
725
+ const label = String(body?.label || '');
726
+ const description = body?.description === undefined || body?.description === null
727
+ ? body?.description
728
+ : String(body.description);
729
+ const publicLabel = body?.publicLabel === undefined || body?.publicLabel === null
730
+ ? body?.publicLabel
731
+ : String(body.publicLabel);
732
+ const publicDescription = body?.publicDescription === undefined || body?.publicDescription === null
733
+ ? body?.publicDescription
734
+ : String(body.publicDescription);
735
+ const publicDescriptionVisible = body?.publicDescriptionVisible === undefined || body?.publicDescriptionVisible === null
736
+ ? body?.publicDescriptionVisible
737
+ : (body.publicDescriptionVisible === false ? false : true);
738
+ const publicDisplayOrder = body?.publicDisplayOrder === undefined || body?.publicDisplayOrder === null || body?.publicDisplayOrder === ''
739
+ ? null
740
+ : Math.max(0, Math.floor(Number(body.publicDisplayOrder)));
741
+ if (!label.trim()) {
742
+ res.writeHead(400, { 'Content-Type': 'application/json' });
743
+ res.end(JSON.stringify({ success: false, error: 'Feature label is required.', code: 'PLAN_FEATURE_LABEL_REQUIRED' }));
744
+ return;
745
+ }
746
+ if (label.trim().length > 120) {
747
+ res.writeHead(400, { 'Content-Type': 'application/json' });
748
+ res.end(JSON.stringify({ success: false, error: 'Feature label must be 120 characters or fewer.', code: 'PLAN_FEATURE_LABEL_TOO_LONG' }));
749
+ return;
750
+ }
751
+ if (typeof description === 'string' && description.trim().length > 280) {
752
+ res.writeHead(400, { 'Content-Type': 'application/json' });
753
+ res.end(JSON.stringify({ success: false, error: 'Feature description must be 280 characters or fewer.', code: 'PLAN_FEATURE_DESCRIPTION_TOO_LONG' }));
754
+ return;
755
+ }
756
+ if (typeof publicLabel === 'string' && publicLabel.trim().length > 120) {
757
+ res.writeHead(400, { 'Content-Type': 'application/json' });
758
+ res.end(JSON.stringify({ success: false, error: 'Public label must be 120 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_LABEL_TOO_LONG' }));
759
+ return;
760
+ }
761
+ if (typeof publicDescription === 'string' && publicDescription.trim().length > 280) {
762
+ res.writeHead(400, { 'Content-Type': 'application/json' });
763
+ res.end(JSON.stringify({ success: false, error: 'Public description must be 280 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_DESCRIPTION_TOO_LONG' }));
764
+ return;
765
+ }
766
+ if (publicDisplayOrder !== null && !Number.isFinite(publicDisplayOrder)) {
767
+ res.writeHead(400, { 'Content-Type': 'application/json' });
768
+ res.end(JSON.stringify({ success: false, error: 'Public display order must be a non-negative number.', code: 'PLAN_FEATURE_PUBLIC_DISPLAY_ORDER_INVALID' }));
769
+ return;
770
+ }
771
+ try {
772
+ const feature = core.createCustomPlanFeatureCatalogEntry({
773
+ label,
774
+ description,
775
+ publicLabel,
776
+ publicDescription,
777
+ publicDescriptionVisible,
778
+ publicDisplayOrder
779
+ });
780
+ auditAdminAction(req, 'platform-plan-feature-catalog-created', {
781
+ featureKey: feature.featureKey,
782
+ label: feature.label,
783
+ description: feature.description,
784
+ publicLabel: feature.publicLabel ?? null,
785
+ publicDescription: feature.publicDescription ?? null,
786
+ publicDescriptionVisible: feature.publicDescriptionVisible ?? null,
787
+ publicDisplayOrder: feature.publicDisplayOrder ?? null
788
+ });
789
+ res.writeHead(200, { 'Content-Type': 'application/json' });
790
+ res.end(JSON.stringify({ success: true, feature }));
791
+ }
792
+ catch (error) {
793
+ const statusCode = resolveErrorStatusCode(error, 400);
794
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
795
+ res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to create feature catalog entry'), code: error?.code }));
796
+ }
797
+ });
715
798
  addRoute('PATCH', '/api/platform/features/:featureKey', async (req, res, params) => {
716
799
  if (!ensureCloudPlanData(req, res))
717
800
  return;
@@ -722,15 +805,34 @@ export function registerAdminRoutes(deps) {
722
805
  }
723
806
  const body = await parseJsonBody(req);
724
807
  const featureKey = String(params.featureKey || '').trim();
808
+ const label = body?.label === undefined || body?.label === null
809
+ ? body?.label
810
+ : String(body.label);
811
+ const description = body?.description === undefined || body?.description === null
812
+ ? body?.description
813
+ : String(body.description);
725
814
  const publicLabel = body?.publicLabel === undefined || body?.publicLabel === null
726
815
  ? body?.publicLabel
727
816
  : String(body.publicLabel);
728
817
  const publicDescription = body?.publicDescription === undefined || body?.publicDescription === null
729
818
  ? body?.publicDescription
730
819
  : String(body.publicDescription);
820
+ const publicDescriptionVisible = body?.publicDescriptionVisible === undefined || body?.publicDescriptionVisible === null
821
+ ? body?.publicDescriptionVisible
822
+ : (body.publicDescriptionVisible === false ? false : true);
731
823
  const publicDisplayOrder = body?.publicDisplayOrder === undefined || body?.publicDisplayOrder === null || body?.publicDisplayOrder === ''
732
824
  ? null
733
825
  : Math.max(0, Math.floor(Number(body.publicDisplayOrder)));
826
+ if (typeof label === 'string' && label.trim().length > 120) {
827
+ res.writeHead(400, { 'Content-Type': 'application/json' });
828
+ res.end(JSON.stringify({ success: false, error: 'Feature label must be 120 characters or fewer.', code: 'PLAN_FEATURE_LABEL_TOO_LONG' }));
829
+ return;
830
+ }
831
+ if (typeof description === 'string' && description.trim().length > 280) {
832
+ res.writeHead(400, { 'Content-Type': 'application/json' });
833
+ res.end(JSON.stringify({ success: false, error: 'Feature description must be 280 characters or fewer.', code: 'PLAN_FEATURE_DESCRIPTION_TOO_LONG' }));
834
+ return;
835
+ }
734
836
  if (typeof publicLabel === 'string' && publicLabel.trim().length > 120) {
735
837
  res.writeHead(400, { 'Content-Type': 'application/json' });
736
838
  res.end(JSON.stringify({ success: false, error: 'Public label must be 120 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_LABEL_TOO_LONG' }));
@@ -749,14 +851,20 @@ export function registerAdminRoutes(deps) {
749
851
  try {
750
852
  const feature = core.updatePlanFeatureCatalogMarketingCopy({
751
853
  featureKey,
854
+ label,
855
+ description,
752
856
  publicLabel,
753
857
  publicDescription,
858
+ publicDescriptionVisible,
754
859
  publicDisplayOrder
755
860
  });
756
861
  auditAdminAction(req, 'platform-plan-feature-catalog-updated', {
757
862
  featureKey,
863
+ label: feature.label,
864
+ description: feature.description,
758
865
  publicLabel: feature.publicLabel ?? null,
759
866
  publicDescription: feature.publicDescription ?? null,
867
+ publicDescriptionVisible: feature.publicDescriptionVisible ?? null,
760
868
  publicDisplayOrder: feature.publicDisplayOrder ?? null
761
869
  });
762
870
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -768,6 +876,27 @@ export function registerAdminRoutes(deps) {
768
876
  res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to update feature catalog entry'), code: error?.code }));
769
877
  }
770
878
  });
879
+ addRoute('DELETE', '/api/platform/features/:featureKey', async (req, res, params) => {
880
+ if (!ensureCloudPlanData(req, res))
881
+ return;
882
+ if (!ensureAdminRole(req)) {
883
+ res.writeHead(403, { 'Content-Type': 'application/json' });
884
+ res.end(JSON.stringify({ error: 'Forbidden: system admin role required.' }));
885
+ return;
886
+ }
887
+ const featureKey = String(params.featureKey || '').trim();
888
+ try {
889
+ core.deleteCustomPlanFeatureCatalogEntry(featureKey);
890
+ auditAdminAction(req, 'platform-plan-feature-catalog-deleted', { featureKey });
891
+ res.writeHead(200, { 'Content-Type': 'application/json' });
892
+ res.end(JSON.stringify({ success: true }));
893
+ }
894
+ catch (error) {
895
+ const statusCode = resolveErrorStatusCode(error, 400);
896
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
897
+ res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to delete feature catalog entry'), code: error?.code }));
898
+ }
899
+ });
771
900
  addRoute('POST', '/api/platform/plans/:planId/versions', async (req, res, params) => {
772
901
  if (!ensureCloudPlanData(req, res))
773
902
  return;
@@ -1298,6 +1427,7 @@ export function registerAdminRoutes(deps) {
1298
1427
  ...(siteSettings.siteTheme === 'light' || siteSettings.siteTheme === 'dark' ? { siteTheme: siteSettings.siteTheme } : {}),
1299
1428
  ...(typeof siteSettings.pricingPageTitle === 'string' ? { pricingPageTitle: siteSettings.pricingPageTitle } : {}),
1300
1429
  ...(typeof siteSettings.pricingPageDescription === 'string' ? { pricingPageDescription: siteSettings.pricingPageDescription } : {}),
1430
+ ...(typeof siteSettings.pricingPageShowPublicDescriptions === 'boolean' ? { pricingPageShowPublicDescriptions: siteSettings.pricingPageShowPublicDescriptions } : {}),
1301
1431
  onboardingPolicyMode: onboardingPolicy.mode,
1302
1432
  onboardingPlanId: onboardingPolicy.planId
1303
1433
  },
@@ -1314,6 +1444,7 @@ export function registerAdminRoutes(deps) {
1314
1444
  siteTheme: effectiveSite.siteTheme,
1315
1445
  pricingPageTitle: effectiveSite.pricingPageTitle,
1316
1446
  pricingPageDescription: effectiveSite.pricingPageDescription,
1447
+ pricingPageShowPublicDescriptions: effectiveSite.pricingPageShowPublicDescriptions,
1317
1448
  onboardingPolicyMode: effective.onboardingPolicyMode,
1318
1449
  onboardingPlanId: effective.onboardingPlanId
1319
1450
  }
@@ -1347,6 +1478,9 @@ export function registerAdminRoutes(deps) {
1347
1478
  : {}),
1348
1479
  ...(typeof siteSettings.pricingPageDescription === 'string'
1349
1480
  ? { pricingPageDescription: siteSettings.pricingPageDescription }
1481
+ : {}),
1482
+ ...(typeof siteSettings.pricingPageShowPublicDescriptions === 'boolean'
1483
+ ? { pricingPageShowPublicDescriptions: siteSettings.pricingPageShowPublicDescriptions }
1350
1484
  : {})
1351
1485
  },
1352
1486
  status: {
@@ -1355,7 +1489,8 @@ export function registerAdminRoutes(deps) {
1355
1489
  animatedBackgroundEnabled: siteBehavior.animatedBackgroundEnabled,
1356
1490
  siteTheme: siteBehavior.siteTheme,
1357
1491
  pricingPageTitle: siteBehavior.pricingPageTitle,
1358
- pricingPageDescription: siteBehavior.pricingPageDescription
1492
+ pricingPageDescription: siteBehavior.pricingPageDescription,
1493
+ pricingPageShowPublicDescriptions: siteBehavior.pricingPageShowPublicDescriptions
1359
1494
  }
1360
1495
  }));
1361
1496
  };
@@ -1990,9 +2125,9 @@ export function registerAdminRoutes(deps) {
1990
2125
  res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
1991
2126
  return;
1992
2127
  }
1993
- if (pricingPageDescription !== undefined && pricingPageDescription.length > 280) {
2128
+ if (pricingPageDescription !== undefined && pricingPageDescription.length > PRICING_PAGE_DESCRIPTION_MAX_LENGTH) {
1994
2129
  res.writeHead(400, { 'Content-Type': 'application/json' });
1995
- res.end(JSON.stringify({ error: 'Pricing page description must be 280 characters or fewer.', code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
2130
+ res.end(JSON.stringify({ error: `Pricing page description must be ${PRICING_PAGE_DESCRIPTION_MAX_LENGTH} characters or fewer.`, code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
1996
2131
  return;
1997
2132
  }
1998
2133
  const validation = core.validateOnboardingPolicy({
@@ -2022,6 +2157,9 @@ export function registerAdminRoutes(deps) {
2022
2157
  : {}),
2023
2158
  ...(pricingPageDescription !== undefined
2024
2159
  ? { pricingPageDescription }
2160
+ : {}),
2161
+ ...(typeof body?.pricingPageShowPublicDescriptions === 'boolean'
2162
+ ? { pricingPageShowPublicDescriptions: body.pricingPageShowPublicDescriptions }
2025
2163
  : {})
2026
2164
  };
2027
2165
  core.updateGlobalSettings({
@@ -2069,9 +2207,9 @@ export function registerAdminRoutes(deps) {
2069
2207
  res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
2070
2208
  return;
2071
2209
  }
2072
- if (pricingPageDescription !== undefined && pricingPageDescription.length > 280) {
2210
+ if (pricingPageDescription !== undefined && pricingPageDescription.length > PRICING_PAGE_DESCRIPTION_MAX_LENGTH) {
2073
2211
  res.writeHead(400, { 'Content-Type': 'application/json' });
2074
- res.end(JSON.stringify({ error: 'Pricing page description must be 280 characters or fewer.', code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
2212
+ res.end(JSON.stringify({ error: `Pricing page description must be ${PRICING_PAGE_DESCRIPTION_MAX_LENGTH} characters or fewer.`, code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
2075
2213
  return;
2076
2214
  }
2077
2215
  core.updateGlobalSettings({
@@ -2093,6 +2231,9 @@ export function registerAdminRoutes(deps) {
2093
2231
  : {}),
2094
2232
  ...(pricingPageDescription !== undefined
2095
2233
  ? { pricingPageDescription }
2234
+ : {}),
2235
+ ...(typeof body?.pricingPageShowPublicDescriptions === 'boolean'
2236
+ ? { pricingPageShowPublicDescriptions: body.pricingPageShowPublicDescriptions }
2096
2237
  : {})
2097
2238
  }
2098
2239
  });
@@ -28,6 +28,7 @@ export declare function buildSiteBehavior(core: TaskforceCore): {
28
28
  siteTheme: string;
29
29
  pricingPageTitle: string;
30
30
  pricingPageDescription: string;
31
+ pricingPageShowPublicDescriptions: boolean;
31
32
  };
32
33
  export declare function shouldExposeDevAuthTokens(): boolean;
33
34
  export declare function resolveRuntimeConfigBaseUrl(req: IncomingMessage): string;
@@ -64,7 +64,8 @@ export function buildSiteBehavior(core) {
64
64
  animatedBackgroundEnabled: resolveBooleanSetting(siteSettings.animatedBackgroundEnabled, false),
65
65
  siteTheme,
66
66
  pricingPageTitle,
67
- pricingPageDescription
67
+ pricingPageDescription,
68
+ pricingPageShowPublicDescriptions: resolveBooleanSetting(siteSettings.pricingPageShowPublicDescriptions, false)
68
69
  };
69
70
  }
70
71
  export function shouldExposeDevAuthTokens() {
@@ -3,6 +3,7 @@ import type { TaskforceCore } from '../../core/Taskforce.js';
3
3
  import { type RouteHandler, type RouteContext } from './primitives.js';
4
4
  export type BillingInterval = 'month' | 'year';
5
5
  export type BillingPricingType = 'stripe' | 'free';
6
+ export type BillingPricingAudience = 'public' | 'campaign';
6
7
  export interface BillingRouteDependencies {
7
8
  addRoute: (method: string, path: string, handler: RouteHandler) => void;
8
9
  core: TaskforceCore;