@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.
- package/dist/Taskforce.module.css +11 -0
- package/dist/TaskforceCore.js +59 -9
- package/dist/TaskforceCore.test.js +717 -14
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +110 -51
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +106 -151
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/Taskforce.d.ts +51 -10
- package/dist/core/Taskforce.js +218 -49
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/types.d.ts +11 -12
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskforce.d.ts +3 -0
- package/dist/hooks/useTaskforce.js +9 -4
- package/dist/mcp/runtime.js +90 -29
- package/dist/mcp/runtime.test.js +272 -81
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.test.js +2 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +80 -23
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +194 -53
- package/dist/server/routes/billing.js +506 -23
- package/dist/server/routes/billing.test.js +937 -45
- package/dist/server/routes/shared.js +5 -3
- package/dist/server/routes/sync.integration.test.js +160 -0
- package/dist/server/routes/sync.js +15 -5
- package/dist/server/routes.js +3 -3
- package/dist/server/routes.test.js +287 -76
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -16
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/workspacePullFeed.d.ts +10 -0
- package/dist/sync/workspacePullFeed.js +111 -63
- package/dist/sync/workspacePullFeed.test.js +112 -0
- package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/package.json +3 -3
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- 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 === '
|
|
171
|
-
? '
|
|
172
|
-
: '
|
|
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
|
package/dist/server/routes.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
361
|
-
|
|
360
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
361
|
+
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
362
362
|
};
|
|
363
363
|
};
|
|
364
364
|
const deliverAuthEmail = async (kind, email, token, req) => {
|