@taskforcehq/taskforce 0.3.302 → 0.3.303

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 (77) hide show
  1. package/dist/Taskforce.module.css +11 -0
  2. package/dist/TaskforceCore.js +59 -9
  3. package/dist/TaskforceCore.test.js +717 -14
  4. package/dist/components/task/TaskKanban.test.js +17 -0
  5. package/dist/components/views/PlansPage.d.ts +3 -1
  6. package/dist/components/views/PlansPage.js +110 -51
  7. package/dist/components/views/PlansPage.test.d.ts +1 -0
  8. package/dist/components/views/PlansPage.test.js +100 -0
  9. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  10. package/dist/components/views/StandaloneLayout.js +106 -151
  11. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  12. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  13. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  14. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  15. package/dist/core/EntitlementsPolicy.test.js +2 -2
  16. package/dist/core/GlobalSettingsService.js +78 -18
  17. package/dist/core/PlanEntitlementService.d.ts +29 -5
  18. package/dist/core/PlanEntitlementService.js +297 -219
  19. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  20. package/dist/core/SystemAdmin.test.js +186 -96
  21. package/dist/core/Taskforce.d.ts +51 -10
  22. package/dist/core/Taskforce.js +218 -49
  23. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  24. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  25. package/dist/core/types.d.ts +11 -12
  26. package/dist/hooks/useSyncOrchestrator.js +17 -3
  27. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  28. package/dist/hooks/useTaskforce.d.ts +3 -0
  29. package/dist/hooks/useTaskforce.js +9 -4
  30. package/dist/mcp/runtime.js +90 -29
  31. package/dist/mcp/runtime.test.js +272 -81
  32. package/dist/migrations/billingSchemaParity.test.js +3 -0
  33. package/dist/migrations/taskSchemaMigrations.js +15 -0
  34. package/dist/server/index.cookieProxy.test.js +1 -0
  35. package/dist/server/index.js +2 -0
  36. package/dist/server/index.test.js +2 -1
  37. package/dist/server/mockStripe.d.ts +1 -0
  38. package/dist/server/mockStripe.js +2 -0
  39. package/dist/server/routes/admin.js +80 -23
  40. package/dist/server/routes/auth.d.ts +1 -0
  41. package/dist/server/routes/auth.js +194 -53
  42. package/dist/server/routes/billing.js +506 -23
  43. package/dist/server/routes/billing.test.js +937 -45
  44. package/dist/server/routes/shared.js +5 -3
  45. package/dist/server/routes/sync.integration.test.js +160 -0
  46. package/dist/server/routes/sync.js +15 -5
  47. package/dist/server/routes.js +3 -3
  48. package/dist/server/routes.test.js +287 -76
  49. package/dist/sync/cloudSyncApi.d.ts +1 -0
  50. package/dist/sync/contentSyncState.d.ts +10 -0
  51. package/dist/sync/contentSyncState.js +277 -16
  52. package/dist/sync/syncService.d.ts +1 -0
  53. package/dist/sync/syncService.js +21 -8
  54. package/dist/sync/workspacePullFeed.d.ts +10 -0
  55. package/dist/sync/workspacePullFeed.js +111 -63
  56. package/dist/sync/workspacePullFeed.test.js +112 -0
  57. package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
  58. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  59. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
  60. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  61. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  62. package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
  63. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  64. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  65. package/dist/ui/assets/index-DneETxcu.css +1 -0
  66. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
  67. package/dist/ui/index.html +3 -3
  68. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  69. package/dist/utils/accountProfileSummaryCache.js +97 -0
  70. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  71. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  72. package/package.json +3 -3
  73. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  74. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  75. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  76. package/dist/utils/billingStatusCache.d.ts +0 -21
  77. package/dist/utils/billingStatusCache.js +0 -70
@@ -167,9 +167,11 @@ export function makeEnsureAppAccess(core, context) {
167
167
  return true;
168
168
  const code = accountAccess.gate === 'plan_selection_required'
169
169
  ? 'PLAN_SELECTION_REQUIRED'
170
- : accountAccess.gate === 'misconfigured'
171
- ? 'ACCOUNT_ACCESS_MISCONFIGURED'
172
- : 'ACCOUNT_ENTITLEMENT_REQUIRED';
170
+ : accountAccess.gate === 'checkout_pending'
171
+ ? 'CHECKOUT_PENDING'
172
+ : accountAccess.gate === 'misconfigured'
173
+ ? 'ACCOUNT_ACCESS_MISCONFIGURED'
174
+ : 'ACCOUNT_ENTITLEMENT_REQUIRED';
173
175
  res.writeHead(403, { 'Content-Type': 'application/json' });
174
176
  res.end(JSON.stringify({
175
177
  error: accountAccess.message || 'This account cannot access the app.',
@@ -1311,6 +1311,166 @@ describe('Server Routes Workspace Sync Integration', () => {
1311
1311
  })
1312
1312
  ]);
1313
1313
  });
1314
+ it('lets the cloud document ref win during normal sync apply and records a diagnostic', async () => {
1315
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-doc-ref-drift-');
1316
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1317
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
1318
+ core.createTask({
1319
+ id: 'task-doc-ref-drift-1',
1320
+ title: 'Task with drifting synced markdown asset',
1321
+ status: 'task'
1322
+ }, {
1323
+ workspaceId,
1324
+ allowClientProvidedId: true
1325
+ });
1326
+ const seededStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
1327
+ seededStore.upsertAsset({
1328
+ assetId: 'asset-doc-ref-drift-1',
1329
+ workspaceId,
1330
+ kind: 'document',
1331
+ originalFilename: 'phase1-ref-drift.md',
1332
+ mimeType: 'text/markdown',
1333
+ storageProvider: 'local',
1334
+ storageKey: 'docs/phase1-ref-drift.md',
1335
+ contentSha256: 'sha-doc-ref-drift-local',
1336
+ sizeBytes: 10,
1337
+ documentId: 'doc-ref-drift-1',
1338
+ referenceNumber: 12,
1339
+ createdAt: '2026-03-17T11:55:00.000Z',
1340
+ updatedAt: '2026-03-17T11:55:00.000Z'
1341
+ });
1342
+ seededStore.upsertLink({
1343
+ workspaceId,
1344
+ assetId: 'asset-doc-ref-drift-1',
1345
+ taskId: 'task-doc-ref-drift-1',
1346
+ role: 'attachment',
1347
+ updatedAt: '2026-03-17T11:55:00.000Z',
1348
+ deletedAt: null
1349
+ });
1350
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
1351
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
1352
+ expect(match).toBeTruthy();
1353
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
1354
+ changes: [
1355
+ {
1356
+ op: 'document-upsert',
1357
+ path: 'docs/phase1-ref-drift.md',
1358
+ updatedAt: '2026-03-17T12:00:00.000Z',
1359
+ content: '# cloud canonical markdown\n',
1360
+ assetId: 'asset-doc-ref-drift-1',
1361
+ documentId: 'doc-ref-drift-1',
1362
+ referenceNumber: 18,
1363
+ version: 2,
1364
+ taskId: 'task-doc-ref-drift-1',
1365
+ originalFilename: 'phase1-ref-drift.md',
1366
+ linkRole: 'attachment'
1367
+ }
1368
+ ]
1369
+ }, { 'x-taskforce-workspace-id': workspaceId });
1370
+ const res = createMockRes();
1371
+ await match?.handler(req, res, {});
1372
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
1373
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1374
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
1375
+ const asset = store.get('asset-doc-ref-drift-1', workspaceId);
1376
+ const syncEvents = reloadedCore.listSyncEvents(workspaceId, 20);
1377
+ expect(asset).toEqual(expect.objectContaining({
1378
+ assetId: 'asset-doc-ref-drift-1',
1379
+ referenceNumber: 18,
1380
+ version: 2
1381
+ }));
1382
+ expect(syncEvents).toEqual(expect.arrayContaining([
1383
+ expect.objectContaining({
1384
+ eventType: 'apply',
1385
+ status: 'error',
1386
+ errorMessage: 'Detected document reference number mismatch during normal sync.',
1387
+ details: expect.objectContaining({
1388
+ assetId: 'asset-doc-ref-drift-1',
1389
+ existingReferenceNumber: 12,
1390
+ incomingReferenceNumber: 18
1391
+ })
1392
+ })
1393
+ ]));
1394
+ });
1395
+ it('lets the cloud image ref win during normal sync apply and records a diagnostic', async () => {
1396
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-image-ref-drift-');
1397
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1398
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
1399
+ core.createTask({
1400
+ id: 'task-image-ref-drift-1',
1401
+ title: 'Task with drifting synced image asset',
1402
+ status: 'task'
1403
+ }, {
1404
+ workspaceId,
1405
+ allowClientProvidedId: true
1406
+ });
1407
+ const seededStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
1408
+ seededStore.upsertAsset({
1409
+ assetId: 'asset-image-ref-drift-1',
1410
+ workspaceId,
1411
+ kind: 'image',
1412
+ originalFilename: 'hero.png',
1413
+ mimeType: 'image/png',
1414
+ storageProvider: 'local',
1415
+ storageKey: 'workspaces/workspace-local-active/assets/images/hero.png',
1416
+ contentSha256: 'sha-image-ref-drift-local',
1417
+ sizeBytes: 10,
1418
+ referenceNumber: 12,
1419
+ createdAt: '2026-03-17T11:55:00.000Z',
1420
+ updatedAt: '2026-03-17T11:55:00.000Z'
1421
+ });
1422
+ seededStore.upsertLink({
1423
+ workspaceId,
1424
+ assetId: 'asset-image-ref-drift-1',
1425
+ taskId: 'task-image-ref-drift-1',
1426
+ role: 'image',
1427
+ updatedAt: '2026-03-17T11:55:00.000Z',
1428
+ deletedAt: null
1429
+ });
1430
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
1431
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
1432
+ expect(match).toBeTruthy();
1433
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
1434
+ changes: [
1435
+ {
1436
+ op: 'asset-upsert',
1437
+ path: 'workspaces/workspace-local-active/assets/images/hero.png',
1438
+ updatedAt: '2026-03-17T12:05:00.000Z',
1439
+ contentBase64: Buffer.from('cloud-hero').toString('base64'),
1440
+ assetId: 'asset-image-ref-drift-1',
1441
+ kind: 'image',
1442
+ mimeType: 'image/png',
1443
+ referenceNumber: 18,
1444
+ taskId: 'task-image-ref-drift-1',
1445
+ originalFilename: 'hero.png',
1446
+ linkRole: 'image'
1447
+ }
1448
+ ]
1449
+ }, { 'x-taskforce-workspace-id': workspaceId });
1450
+ const res = createMockRes();
1451
+ await match?.handler(req, res, {});
1452
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
1453
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1454
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
1455
+ const asset = store.get('asset-image-ref-drift-1', workspaceId);
1456
+ const syncEvents = reloadedCore.listSyncEvents(workspaceId, 20);
1457
+ expect(asset).toEqual(expect.objectContaining({
1458
+ assetId: 'asset-image-ref-drift-1',
1459
+ referenceNumber: 18
1460
+ }));
1461
+ expect(syncEvents).toEqual(expect.arrayContaining([
1462
+ expect.objectContaining({
1463
+ eventType: 'apply',
1464
+ status: 'error',
1465
+ errorMessage: 'Detected asset reference number mismatch during normal sync.',
1466
+ details: expect.objectContaining({
1467
+ assetId: 'asset-image-ref-drift-1',
1468
+ existingReferenceNumber: 12,
1469
+ incomingReferenceNumber: 18
1470
+ })
1471
+ })
1472
+ ]));
1473
+ });
1314
1474
  it('round-trips cloud attachment links for canonical markdown and file assets into local apply', async () => {
1315
1475
  const cloudProjectRoot = createIsolatedProjectRoot('taskforce-sync-cloud-link-roundtrip-cloud-');
1316
1476
  const localProjectRoot = createIsolatedProjectRoot('taskforce-sync-cloud-link-roundtrip-local-');
@@ -771,10 +771,18 @@ export function registerSyncRoutes(deps) {
771
771
  })),
772
772
  ...incomingDocuments
773
773
  .map((doc) => serializeDocumentSyncPayload(doc))
774
- .filter((doc) => Boolean(doc && doc.path.length > 0)),
774
+ .filter((doc) => Boolean(doc && doc.path.length > 0))
775
+ .map((doc) => ({
776
+ op: 'document-upsert',
777
+ ...doc
778
+ })),
775
779
  ...incomingAssets
776
780
  .map((asset) => serializeBinaryAssetSyncPayload(asset))
777
- .filter((asset) => Boolean(asset && asset.path.length > 0 && asset.contentBase64.length > 0)),
781
+ .filter((asset) => Boolean(asset && asset.path.length > 0 && asset.contentBase64.length > 0))
782
+ .map((asset) => ({
783
+ op: 'asset-upsert',
784
+ ...asset
785
+ })),
778
786
  ...incomingDocumentReviewSessions
779
787
  .map((session) => normalizeDocumentReviewSessionSyncPayload(session))
780
788
  .filter((session) => Boolean(session && session.id.length > 0 && session.assetId.length > 0))
@@ -1060,7 +1068,7 @@ export function registerSyncRoutes(deps) {
1060
1068
  const pageSize = Number.isFinite(requestedLimit) && requestedLimit > 0
1061
1069
  ? Math.max(1, Math.min(500, Math.floor(requestedLimit)))
1062
1070
  : 200;
1063
- const { workspaceMembers, changes, nextCursor, hasMore } = await buildWorkspacePullFeed({
1071
+ const { workspaceMembers, changes, nextCursor, hasMore, diagnostics } = await buildWorkspacePullFeed({
1064
1072
  core,
1065
1073
  workspaceId: targetWorkspaceId,
1066
1074
  cursor,
@@ -1076,12 +1084,14 @@ export function registerSyncRoutes(deps) {
1076
1084
  workspaceMembers,
1077
1085
  changes,
1078
1086
  nextCursor,
1079
- hasMore
1087
+ hasMore,
1088
+ diagnostics
1080
1089
  }));
1081
1090
  auditAuthAction(req, 'sync-workspace-pull-success', {
1082
1091
  workspaceId: targetWorkspaceId,
1083
1092
  returnedChanges: changes.length,
1084
- hasMore
1093
+ hasMore,
1094
+ diagnostics
1085
1095
  });
1086
1096
  });
1087
1097
  // POST /api/taskforce/sync/workspace/apply-local - Apply pulled cloud deltas into local workspace store
@@ -345,7 +345,7 @@ export function createRoutes(core, context = {}) {
345
345
  const authSettings = globalSettings.auth || {};
346
346
  const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
347
347
  const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
348
- const signupSettings = core.getSignupMonetizationSettings();
348
+ const onboardingPolicy = core.getOnboardingPolicy();
349
349
  return {
350
350
  allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
351
351
  requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
@@ -357,8 +357,8 @@ export function createRoutes(core, context = {}) {
357
357
  authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
358
358
  ? Math.floor(authRateLimitWindowMsRaw)
359
359
  : 15 * 60_000,
360
- signupMonetizationStrategy: signupSettings.strategy,
361
- defaultSignupPlanVersionId: signupSettings.defaultSignupPlanVersionId
360
+ onboardingPolicyMode: onboardingPolicy.mode,
361
+ onboardingPlanVersionId: onboardingPolicy.planVersionId
362
362
  };
363
363
  };
364
364
  const deliverAuthEmail = async (kind, email, token, req) => {