@taskforcehq/taskforce 0.3.304 → 0.3.305
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 +140 -247
- package/dist/TaskforceCore.js +165 -70
- package/dist/TaskforceCore.test.js +799 -90
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +358 -221
- package/dist/components/task/TaskForm.test.js +456 -381
- package/dist/components/task/TaskKanban.js +2 -1
- package/dist/components/views/PlanComparisonPage.js +5 -8
- package/dist/components/views/PlanComparisonPage.test.js +51 -41
- package/dist/components/views/PlansPage.js +126 -84
- package/dist/components/views/PlansPage.test.js +308 -39
- package/dist/components/views/StandaloneLayout.js +28 -22
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +7 -1
- package/dist/core/PlanEntitlementService.js +117 -42
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +46 -21
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +3 -0
- package/dist/core/Taskforce.js +50 -10
- package/dist/core/WorkspacePlanMode.test.js +12 -12
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +8 -4
- package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
- package/dist/hooks/useSyncOrchestrator.js +75 -7
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +5 -2
- package/dist/hooks/useTaskforce.js +17 -5
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
- package/dist/hooks/useWorkspaceSyncController.js +11 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.js +2362 -3530
- package/dist/mcp/runtime.test.js +159 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +138 -61
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +23 -63
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +104 -188
- package/dist/server/routes/billing.test.js +214 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.js +1 -1
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +1 -1
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +76 -162
- package/dist/server/routes.test.js +672 -30
- package/dist/sync/contentSyncState.js +16 -7
- package/dist/sync/syncApplyHandlers.test.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +8 -0
- package/dist/sync/syncService.test.js +14 -0
- package/dist/sync/workspaceSyncModel.d.ts +3 -0
- package/dist/sync/workspaceSyncModel.js +12 -3
- package/dist/sync/workspaceSyncModel.test.js +34 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/package.json +8 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
|
@@ -348,6 +348,59 @@ describe('TaskforceCore taxonomy validation', () => {
|
|
|
348
348
|
core.close();
|
|
349
349
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
350
350
|
});
|
|
351
|
+
it('migrates legacy config-backed taxonomy data once and no longer depends on config for runtime state', () => {
|
|
352
|
+
const projectRoot = makeTempProjectRoot();
|
|
353
|
+
fs.mkdirSync(path.join(projectRoot, '.taskforce'), { recursive: true });
|
|
354
|
+
const configPath = path.join(projectRoot, '.taskforce', 'config.json');
|
|
355
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
356
|
+
categories: [{ value: 'legacy', label: 'Legacy' }],
|
|
357
|
+
types: [{ value: 'legacy-type', label: 'Legacy Type' }],
|
|
358
|
+
priorities: [{ value: 9, label: 'Legacy Priority', color: 'red-500', icon: 'AlertTriangle' }],
|
|
359
|
+
taxonomies: [{ id: 'legacy-taxonomy', label: 'Legacy Taxonomy', widgetType: 'select', isSystem: false, multiSelect: false, options: [] }]
|
|
360
|
+
}, null, 2));
|
|
361
|
+
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
362
|
+
const migratedState = core.getTaxonomyState('default');
|
|
363
|
+
expect((migratedState.categories || []).map((category) => (typeof category === 'string' ? category : category.value))).toEqual(['default', 'legacy']);
|
|
364
|
+
expect((migratedState.types || []).map((type) => type.value)).toEqual(['legacy-type']);
|
|
365
|
+
expect((migratedState.priorities || []).map((priority) => priority.value)).toEqual([9]);
|
|
366
|
+
expect((migratedState.taxonomies || []).map((taxonomy) => taxonomy.id)).toEqual(['legacy-taxonomy']);
|
|
367
|
+
core.close();
|
|
368
|
+
fs.writeFileSync(configPath, JSON.stringify({ checklistDropdownEnabled: true }, null, 2));
|
|
369
|
+
const reopened = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
370
|
+
const persistedState = reopened.getTaxonomyState('default');
|
|
371
|
+
expect((persistedState.categories || []).map((category) => (typeof category === 'string' ? category : category.value))).toEqual(['default', 'legacy']);
|
|
372
|
+
expect((persistedState.types || []).map((type) => type.value)).toEqual(['legacy-type']);
|
|
373
|
+
expect((persistedState.priorities || []).map((priority) => priority.value)).toEqual([9]);
|
|
374
|
+
expect((persistedState.taxonomies || []).map((taxonomy) => taxonomy.id)).toEqual(['legacy-taxonomy']);
|
|
375
|
+
reopened.close();
|
|
376
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
377
|
+
});
|
|
378
|
+
it('does not seed new workspaces from legacy config-backed taxonomy definitions', () => {
|
|
379
|
+
const projectRoot = makeTempProjectRoot();
|
|
380
|
+
fs.mkdirSync(path.join(projectRoot, '.taskforce'), { recursive: true });
|
|
381
|
+
const configPath = path.join(projectRoot, '.taskforce', 'config.json');
|
|
382
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
383
|
+
categories: [{ value: 'legacy', label: 'Legacy' }],
|
|
384
|
+
types: [{ value: 'legacy-type', label: 'Legacy Type' }],
|
|
385
|
+
priorities: [{ value: 9, label: 'Legacy Priority' }],
|
|
386
|
+
taxonomies: [{ id: 'legacy-taxonomy', label: 'Legacy Taxonomy' }]
|
|
387
|
+
}, null, 2));
|
|
388
|
+
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
389
|
+
core.createWorkspace({ workspaceId: 'workspace-new', name: 'New Workspace' });
|
|
390
|
+
const state = core.getTaxonomyState('workspace-new');
|
|
391
|
+
expect((state.categories || []).map((category) => (typeof category === 'string' ? category : category.value))).toEqual(['default']);
|
|
392
|
+
expect((state.types || []).map((type) => type.value)).toEqual([
|
|
393
|
+
'task',
|
|
394
|
+
'deliverable',
|
|
395
|
+
'issue',
|
|
396
|
+
'idea',
|
|
397
|
+
'review'
|
|
398
|
+
]);
|
|
399
|
+
expect((state.priorities || []).map((priority) => priority.value)).toEqual([1, 2, 3]);
|
|
400
|
+
expect(state.taxonomies || []).toEqual([]);
|
|
401
|
+
core.close();
|
|
402
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
403
|
+
});
|
|
351
404
|
it('preserves existing first-class taxonomy sections when taxonomy state updates are partial', () => {
|
|
352
405
|
const projectRoot = makeTempProjectRoot();
|
|
353
406
|
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
package/dist/core/Taskforce.d.ts
CHANGED
|
@@ -911,6 +911,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
911
911
|
getAccountAccessStatus(accountId: string): import("./types.js").AccountAccessStatus;
|
|
912
912
|
getEffectivePlanVersion(accountId: string): import("./types.js").PlanVersionSnapshot;
|
|
913
913
|
getAccountEntitlement(accountId: string): import("./types.js").AccountEntitlementSnapshot;
|
|
914
|
+
countAiProfileUsage(workspaceId: string): import("./types.js").SeatUsageSnapshot;
|
|
914
915
|
countWorkspaceSeatUsage(workspaceId: string, options?: {
|
|
915
916
|
includeInvited?: boolean;
|
|
916
917
|
}): import("./types.js").SeatUsageSnapshot;
|
|
@@ -968,6 +969,8 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
968
969
|
private resolveDefaultTypeValue;
|
|
969
970
|
private getLegacyTaxonomiesConfig;
|
|
970
971
|
private getLegacyPrioritiesConfig;
|
|
972
|
+
private readLegacyWorkspaceTaxonomyStateFromConfig;
|
|
973
|
+
private migrateLegacyWorkspaceTaxonomyStateIfNeeded;
|
|
971
974
|
private buildEmergencyWorkspaceTaxonomyState;
|
|
972
975
|
private getConfiguredDefaultStarterTaxonomyPackId;
|
|
973
976
|
private getResolvedStarterWorkspaceTaxonomyState;
|
package/dist/core/Taskforce.js
CHANGED
|
@@ -79,7 +79,7 @@ const DEFAULT_PLAN_FEATURE_CATALOG = [
|
|
|
79
79
|
featureKey: DOCUMENT_CLOUD_STORAGE_FEATURE_KEY,
|
|
80
80
|
label: 'Document Cloud Storage',
|
|
81
81
|
description: 'Allow cloud document storage with an optional storage allowance in MB.',
|
|
82
|
-
allowedAccessModes: ['enabled', 'disabled'],
|
|
82
|
+
allowedAccessModes: ['enabled', 'limited', 'disabled'],
|
|
83
83
|
configTemplate: {
|
|
84
84
|
[DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY]: 1024
|
|
85
85
|
}
|
|
@@ -1261,6 +1261,9 @@ export class TaskforceCore {
|
|
|
1261
1261
|
getAccountEntitlement(accountId) {
|
|
1262
1262
|
return this.planEntitlementService.getAccountEntitlement(accountId);
|
|
1263
1263
|
}
|
|
1264
|
+
countAiProfileUsage(workspaceId) {
|
|
1265
|
+
return this.planEntitlementService.countAiProfileUsage(workspaceId);
|
|
1266
|
+
}
|
|
1264
1267
|
countWorkspaceSeatUsage(workspaceId, options = {}) {
|
|
1265
1268
|
return this.planEntitlementService.countWorkspaceSeatUsage(workspaceId, options);
|
|
1266
1269
|
}
|
|
@@ -2414,6 +2417,42 @@ export class TaskforceCore {
|
|
|
2414
2417
|
}
|
|
2415
2418
|
return [];
|
|
2416
2419
|
}
|
|
2420
|
+
readLegacyWorkspaceTaxonomyStateFromConfig() {
|
|
2421
|
+
const legacyCategories = this.getLegacyCategoriesConfig();
|
|
2422
|
+
const legacyTypes = this.getLegacyTypesConfig();
|
|
2423
|
+
const legacyPriorities = this.getLegacyPrioritiesConfig();
|
|
2424
|
+
const legacyTaxonomies = this.getLegacyTaxonomiesConfig();
|
|
2425
|
+
if (legacyCategories.length === 0
|
|
2426
|
+
&& legacyTypes.length === 0
|
|
2427
|
+
&& legacyPriorities.length === 0
|
|
2428
|
+
&& legacyTaxonomies.length === 0) {
|
|
2429
|
+
return null;
|
|
2430
|
+
}
|
|
2431
|
+
return normalizeWorkspaceTaxonomyStatePayload({
|
|
2432
|
+
categories: legacyCategories,
|
|
2433
|
+
types: legacyTypes,
|
|
2434
|
+
priorities: legacyPriorities,
|
|
2435
|
+
taxonomies: legacyTaxonomies
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
migrateLegacyWorkspaceTaxonomyStateIfNeeded(workspaceId) {
|
|
2439
|
+
const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
|
|
2440
|
+
if (normalizedWorkspaceId !== 'default') {
|
|
2441
|
+
return null;
|
|
2442
|
+
}
|
|
2443
|
+
const stored = this.readStoredWorkspaceTaxonomyState(normalizedWorkspaceId);
|
|
2444
|
+
if (Object.keys(stored.state).length > 0) {
|
|
2445
|
+
return stored;
|
|
2446
|
+
}
|
|
2447
|
+
const legacyState = this.readLegacyWorkspaceTaxonomyStateFromConfig();
|
|
2448
|
+
if (!legacyState)
|
|
2449
|
+
return null;
|
|
2450
|
+
const persisted = this.writeWorkspaceTaxonomyState(legacyState, normalizedWorkspaceId);
|
|
2451
|
+
return {
|
|
2452
|
+
state: persisted.state,
|
|
2453
|
+
updatedAt: persisted.updatedAt
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2417
2456
|
buildEmergencyWorkspaceTaxonomyState() {
|
|
2418
2457
|
const emergencyPack = getEmergencyStarterTaxonomyLibraryPack();
|
|
2419
2458
|
return {
|
|
@@ -2454,17 +2493,13 @@ export class TaskforceCore {
|
|
|
2454
2493
|
}
|
|
2455
2494
|
getEffectiveWorkspaceTaxonomyState(workspaceId) {
|
|
2456
2495
|
const { state } = this.readStoredWorkspaceTaxonomyState(workspaceId);
|
|
2457
|
-
const legacyCategories = this.getLegacyCategoriesConfig();
|
|
2458
|
-
const legacyTypes = this.getLegacyTypesConfig();
|
|
2459
|
-
const legacyPriorities = this.getLegacyPrioritiesConfig();
|
|
2460
|
-
const legacyTaxonomies = this.getLegacyTaxonomiesConfig();
|
|
2461
2496
|
const starterState = this.getResolvedStarterWorkspaceTaxonomyState();
|
|
2462
2497
|
const emergencyState = this.buildEmergencyWorkspaceTaxonomyState();
|
|
2463
2498
|
return normalizeWorkspaceTaxonomyStatePayload({
|
|
2464
|
-
categories: state.categories ||
|
|
2465
|
-
types: state.types ||
|
|
2466
|
-
priorities: state.priorities ||
|
|
2467
|
-
taxonomies: state.taxonomies ||
|
|
2499
|
+
categories: state.categories || starterState.categories || emergencyState.categories,
|
|
2500
|
+
types: state.types || starterState.types || emergencyState.types,
|
|
2501
|
+
priorities: state.priorities || starterState.priorities || emergencyState.priorities,
|
|
2502
|
+
taxonomies: state.taxonomies || starterState.taxonomies || emergencyState.taxonomies
|
|
2468
2503
|
});
|
|
2469
2504
|
}
|
|
2470
2505
|
ensureWorkspaceTaxonomyStateSeeded(workspaceId) {
|
|
@@ -2473,6 +2508,10 @@ export class TaskforceCore {
|
|
|
2473
2508
|
if (Object.keys(stored.state).length > 0) {
|
|
2474
2509
|
return stored;
|
|
2475
2510
|
}
|
|
2511
|
+
const migratedLegacy = this.migrateLegacyWorkspaceTaxonomyStateIfNeeded(normalizedWorkspaceId);
|
|
2512
|
+
if (migratedLegacy) {
|
|
2513
|
+
return migratedLegacy;
|
|
2514
|
+
}
|
|
2476
2515
|
const seededState = this.getEffectiveWorkspaceTaxonomyState(normalizedWorkspaceId);
|
|
2477
2516
|
const persisted = this.writeWorkspaceTaxonomyState(seededState, normalizedWorkspaceId);
|
|
2478
2517
|
return {
|
|
@@ -4912,7 +4951,8 @@ export class TaskforceCore {
|
|
|
4912
4951
|
aiProfileRowToRecord: (row) => this.aiProfileRowToRecord(row),
|
|
4913
4952
|
generateEntityId: (prefix) => this.generateEntityId(prefix),
|
|
4914
4953
|
issueProfileToken: () => `tfp_${randomBytes(24).toString('hex')}`,
|
|
4915
|
-
|
|
4954
|
+
countAiProfileUsage: (workspaceId) => this.countAiProfileUsage(workspaceId),
|
|
4955
|
+
createRuleError: (message, statusCode = 400, code = 'TASK_RULE_VIOLATION', details) => new TaskforceRuleError(message, statusCode, code, details),
|
|
4916
4956
|
};
|
|
4917
4957
|
}
|
|
4918
4958
|
resolveWorkspaceActor(workspaceId, actorRef) {
|
|
@@ -20,30 +20,30 @@ function openCore(projectRoot) {
|
|
|
20
20
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21
21
|
`).run('default', planVersionId, 'month', `free:${planVersionId}:month`, 'free', 1, 0, null, now, now);
|
|
22
22
|
};
|
|
23
|
-
core.upsertPlanCatalog({ planId: '
|
|
24
|
-
addFreeMapping('
|
|
25
|
-
core.upsertPlanCatalog({ planId: '
|
|
23
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free' });
|
|
24
|
+
addFreeMapping('free-v1');
|
|
25
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
26
26
|
core.updatePlanVersion({
|
|
27
|
-
planId: '
|
|
27
|
+
planId: 'free',
|
|
28
28
|
versionNumber: 1,
|
|
29
29
|
seatLimit: 1
|
|
30
30
|
});
|
|
31
31
|
core.updatePlanVersionFeatures({
|
|
32
|
-
planVersionId: '
|
|
32
|
+
planVersionId: 'free-v1',
|
|
33
33
|
features: [
|
|
34
34
|
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
35
35
|
]
|
|
36
36
|
});
|
|
37
|
-
core.upsertPlanCatalog({ planId: '
|
|
38
|
-
addFreeMapping('
|
|
39
|
-
core.upsertPlanCatalog({ planId: '
|
|
37
|
+
core.upsertPlanCatalog({ planId: 'team-5', displayName: 'Team 5' });
|
|
38
|
+
addFreeMapping('team-5-v1');
|
|
39
|
+
core.upsertPlanCatalog({ planId: 'team-5', displayName: 'Team 5', status: 'active' });
|
|
40
40
|
core.updatePlanVersion({
|
|
41
|
-
planId: '
|
|
41
|
+
planId: 'team-5',
|
|
42
42
|
versionNumber: 1,
|
|
43
43
|
seatLimit: 5
|
|
44
44
|
});
|
|
45
45
|
core.updatePlanVersionFeatures({
|
|
46
|
-
planVersionId: '
|
|
46
|
+
planVersionId: 'team-5-v1',
|
|
47
47
|
features: [
|
|
48
48
|
{ featureKey: 'collaboration.invites', access: 'enabled' },
|
|
49
49
|
{ featureKey: 'collaboration.team_management', access: 'enabled' }
|
|
@@ -233,7 +233,7 @@ describe('TaskforceCore account-level plan mode and invite revoke', () => {
|
|
|
233
233
|
billing: {
|
|
234
234
|
onboardingPolicy: {
|
|
235
235
|
mode: 'auto_grant_free',
|
|
236
|
-
planVersionId: '
|
|
236
|
+
planVersionId: 'free-v1'
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
});
|
|
@@ -266,7 +266,7 @@ describe('TaskforceCore account-level plan mode and invite revoke', () => {
|
|
|
266
266
|
expect(accepted.workspaceId).toBe('workspace-invite');
|
|
267
267
|
expect(core.hasAccountEntitlement('invited-user')).toBe(true);
|
|
268
268
|
expect(core.getAccountEntitlement('invited-user')).toEqual(expect.objectContaining({
|
|
269
|
-
planVersionId: '
|
|
269
|
+
planVersionId: 'free-v1',
|
|
270
270
|
state: 'active'
|
|
271
271
|
}));
|
|
272
272
|
expect(core.getAccountAccessStatus('invited-user')).toEqual(expect.objectContaining({
|
package/dist/core/shared.d.ts
CHANGED
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
import type { McpAccessTokenScope, PlanFeatureCatalogItem } from './types.js';
|
|
18
18
|
export declare const DOCUMENT_CLOUD_STORAGE_FEATURE_KEY = "context.uploads";
|
|
19
19
|
export declare const WORKSPACES_FEATURE_KEY = "workspace.switching";
|
|
20
|
+
export declare const AI_PROFILES_FEATURE_KEY = "collaboration.ai_profiles";
|
|
20
21
|
export declare const DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY = "storageLimitMb";
|
|
21
22
|
export declare const WORKSPACES_LIMIT_CONFIG_KEY = "maxWorkspaces";
|
|
23
|
+
export declare const AI_PROFILES_LIMIT_CONFIG_KEY = "maxAiProfiles";
|
|
22
24
|
export declare const MCP_ACCESS_TOKEN_MAX_ACTIVE_PER_USER_WORKSPACE = 10;
|
|
23
25
|
export declare const MCP_ACCESS_TOKEN_DEFAULT_PAGE_SIZE = 20;
|
|
24
26
|
export declare const MCP_ACCESS_TOKEN_MAX_PAGE_SIZE = 100;
|
package/dist/core/shared.js
CHANGED
|
@@ -18,8 +18,10 @@ import { createHash, pbkdf2Sync, randomBytes, timingSafeEqual } from 'crypto';
|
|
|
18
18
|
// === SECTION: Constants ===
|
|
19
19
|
export const DOCUMENT_CLOUD_STORAGE_FEATURE_KEY = 'context.uploads';
|
|
20
20
|
export const WORKSPACES_FEATURE_KEY = 'workspace.switching';
|
|
21
|
+
export const AI_PROFILES_FEATURE_KEY = 'collaboration.ai_profiles';
|
|
21
22
|
export const DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY = 'storageLimitMb';
|
|
22
23
|
export const WORKSPACES_LIMIT_CONFIG_KEY = 'maxWorkspaces';
|
|
24
|
+
export const AI_PROFILES_LIMIT_CONFIG_KEY = 'maxAiProfiles';
|
|
23
25
|
export const MCP_ACCESS_TOKEN_MAX_ACTIVE_PER_USER_WORKSPACE = 10;
|
|
24
26
|
export const MCP_ACCESS_TOKEN_DEFAULT_PAGE_SIZE = 20;
|
|
25
27
|
export const MCP_ACCESS_TOKEN_MAX_PAGE_SIZE = 100;
|
|
@@ -50,6 +52,15 @@ export const DEFAULT_PLAN_FEATURE_CATALOG = [
|
|
|
50
52
|
configTemplate: {
|
|
51
53
|
[WORKSPACES_LIMIT_CONFIG_KEY]: 1
|
|
52
54
|
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
featureKey: AI_PROFILES_FEATURE_KEY,
|
|
58
|
+
label: 'AI Profiles',
|
|
59
|
+
description: 'Allow registered AI collaborator profiles with an optional per-workspace limit.',
|
|
60
|
+
allowedAccessModes: ['enabled', 'limited', 'disabled'],
|
|
61
|
+
configTemplate: {
|
|
62
|
+
[AI_PROFILES_LIMIT_CONFIG_KEY]: 1
|
|
63
|
+
}
|
|
53
64
|
}
|
|
54
65
|
];
|
|
55
66
|
export const PLAN_FEATURE_DEPENDENCIES = Object.freeze(DEFAULT_PLAN_FEATURE_CATALOG.reduce((acc, feature) => {
|
package/dist/hooks/sync/auth.js
CHANGED
|
@@ -49,7 +49,9 @@ export function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPha
|
|
|
49
49
|
if (ensured.transient || isTransientWorkspaceSyncStatus(ensured.statusCode)) {
|
|
50
50
|
scheduleWorkspaceSyncRetry(ensured.retryAfterMs);
|
|
51
51
|
}
|
|
52
|
-
else if (workspaceSyncPhase === 'active'
|
|
52
|
+
else if (workspaceSyncPhase === 'active'
|
|
53
|
+
|| workspaceSyncPhase === 'attach-cloud'
|
|
54
|
+
|| workspaceSyncPhase === 'provision-local') {
|
|
53
55
|
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
54
56
|
}
|
|
55
57
|
return false;
|
|
@@ -74,6 +74,7 @@ interface CreateWorkspaceTransferCoordinatorArgs {
|
|
|
74
74
|
workspacePendingSignatureRef: MutableRefObject<string>;
|
|
75
75
|
workspaceLastPushedTaskIdsRef: MutableRefObject<Set<string>>;
|
|
76
76
|
workspaceDeletedTaskIdsRef: MutableRefObject<Set<string>>;
|
|
77
|
+
workspaceDeletedTaskWatermarksRef: MutableRefObject<Map<string, string>>;
|
|
77
78
|
workspacePullCursorRef: MutableRefObject<string | null>;
|
|
78
79
|
workspaceLastPushedWatermarksRef: MutableRefObject<Map<string, string>>;
|
|
79
80
|
workspaceLastPushedInitiativeIdsRef: MutableRefObject<Set<string>>;
|
|
@@ -109,7 +110,7 @@ interface CreateWorkspaceTransferCoordinatorArgs {
|
|
|
109
110
|
setTasks: Dispatch<SetStateAction<any[]>>;
|
|
110
111
|
setArchivedTasks: Dispatch<SetStateAction<any[]>>;
|
|
111
112
|
}
|
|
112
|
-
export declare function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }: CreateWorkspaceTransferCoordinatorArgs): {
|
|
113
|
+
export declare function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }: CreateWorkspaceTransferCoordinatorArgs): {
|
|
113
114
|
pushWorkspaceChangesToCloud: (signature: string, options?: {
|
|
114
115
|
forceAllAiProfiles?: boolean;
|
|
115
116
|
repairMode?: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { applyLocalWorkspaceChanges } from '../../sync/cloudSyncApi';
|
|
2
2
|
import { isTransientWorkspaceSyncStatus, runWorkspacePullSyncService, runWorkspacePushSyncService } from '../../sync/syncService';
|
|
3
3
|
import { logSyncDebug, savePersistedSyncWatermarks, isValidWorkspaceKey } from './orchestratorShared';
|
|
4
|
-
export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }) {
|
|
4
|
+
export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }) {
|
|
5
5
|
const pushWorkspaceChangesToCloud = async (signature, options) => {
|
|
6
6
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
7
7
|
return false;
|
|
@@ -115,6 +115,7 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
115
115
|
if (pushResult.deleteTaskIds.size > 0) {
|
|
116
116
|
for (const taskId of pushResult.deleteTaskIds) {
|
|
117
117
|
workspaceDeletedTaskIdsRef.current.delete(taskId);
|
|
118
|
+
workspaceDeletedTaskWatermarksRef.current.delete(taskId);
|
|
118
119
|
workspaceLastPushedWatermarksRef.current.delete(taskId);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -214,12 +215,15 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
214
215
|
setWorkspaceSyncBusy(workspacePullInFlightRef.current);
|
|
215
216
|
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
216
217
|
const pendingSignature = workspacePendingSignatureRef.current;
|
|
218
|
+
const latestSignature = buildWorkspaceSyncSignature();
|
|
217
219
|
if (pendingSignature
|
|
218
|
-
&&
|
|
219
|
-
&&
|
|
220
|
+
&& latestSignature
|
|
221
|
+
&& latestSignature !== workspaceLastPushedSignatureRef.current
|
|
222
|
+
&& !workspacePullInFlightRef.current
|
|
223
|
+
&& !isWorkspaceRetryPending()) {
|
|
220
224
|
workspacePendingSignatureRef.current = '';
|
|
221
225
|
window.setTimeout(() => {
|
|
222
|
-
void pushWorkspaceChangesToCloud(
|
|
226
|
+
void pushWorkspaceChangesToCloud(latestSignature);
|
|
223
227
|
}, 120);
|
|
224
228
|
}
|
|
225
229
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { InitiativeItem, TaskItem, TaskforceTheme, WorkstreamItem } from '../types';
|
|
2
|
-
import { type WorkspaceSyncPhase } from '../sync/workspaceSyncState';
|
|
1
|
+
import type { DeletedTaskRecord, InitiativeItem, TaskItem, TaskforceTheme, WorkstreamItem } from '../types';
|
|
2
|
+
import { type WorkspaceSyncPhase, type WorkspaceSyncSetupIntent } from '../sync/workspaceSyncState';
|
|
3
3
|
import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
|
|
4
4
|
import type { SupportedLocale } from '../localization';
|
|
5
5
|
import { normalizeWorkspaceSyncAiProfileSnapshotEntry } from './sync/orchestratorShared';
|
|
@@ -16,6 +16,7 @@ export interface UseSyncOrchestratorInput {
|
|
|
16
16
|
realtimeSyncEnabled: boolean;
|
|
17
17
|
tasks: TaskItem[];
|
|
18
18
|
archivedTasks: TaskItem[];
|
|
19
|
+
deletedTasks?: DeletedTaskRecord[];
|
|
19
20
|
initiatives: InitiativeItem[];
|
|
20
21
|
workstreams: WorkstreamItem[];
|
|
21
22
|
taxonomies: unknown[];
|
|
@@ -53,6 +54,7 @@ export declare function useSyncOrchestrator(input: UseSyncOrchestratorInput): {
|
|
|
53
54
|
workspaceLastSuccessfulSyncAt: string | null;
|
|
54
55
|
workspaceCloudSyncEnabled: boolean;
|
|
55
56
|
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
57
|
+
workspaceSyncSetupIntent: WorkspaceSyncSetupIntent;
|
|
56
58
|
workspaceSyncStatus: WorkspaceSyncStatus;
|
|
57
59
|
workspaceSyncSummary: string;
|
|
58
60
|
workspaceSyncRecommendedAction: string;
|
|
@@ -67,14 +69,15 @@ export declare function useSyncOrchestrator(input: UseSyncOrchestratorInput): {
|
|
|
67
69
|
workspacePendingSignatureRef: import("react").RefObject<string>;
|
|
68
70
|
workspaceLastPushedTaskIdsRef: import("react").RefObject<Set<string>>;
|
|
69
71
|
workspaceDeletedTaskIdsRef: import("react").RefObject<Set<string>>;
|
|
72
|
+
workspaceDeletedTaskWatermarksRef: import("react").RefObject<Map<string, string>>;
|
|
70
73
|
workspacePullCursorRef: import("react").RefObject<string | null>;
|
|
71
74
|
clearWorkspaceRetry: () => void;
|
|
72
75
|
persistWorkspaceSyncPatch: (patch: Partial<import("../sync/workspaceSyncState").WorkspaceSyncStateV2>) => Promise<import("../sync/workspaceSyncState").WorkspaceSyncStateV2>;
|
|
73
76
|
loadWorkspaceSyncState: () => Promise<void>;
|
|
74
|
-
applyWorkspaceSyncStateSnapshot: (nextState: Partial<import("../sync/workspaceSyncState").WorkspaceSyncStateV2> & {
|
|
77
|
+
applyWorkspaceSyncStateSnapshot: (nextState: Parameters<(nextState: Partial<import("../sync/workspaceSyncState").WorkspaceSyncStateV2> & {
|
|
75
78
|
enabled: boolean;
|
|
76
79
|
phase?: WorkspaceSyncPhase | null;
|
|
77
|
-
}) => void;
|
|
80
|
+
}) => void>[0]) => void;
|
|
78
81
|
syncUserGlobalSettings: (options?: {
|
|
79
82
|
preferCloudOnFirstSync?: boolean;
|
|
80
83
|
}) => Promise<void>;
|
|
@@ -86,7 +86,7 @@ const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
|
|
|
86
86
|
};
|
|
87
87
|
export { normalizeWorkspaceSyncAiProfileSnapshotEntry };
|
|
88
88
|
export function useSyncOrchestrator(input) {
|
|
89
|
-
const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
|
|
89
|
+
const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, deletedTasks = [], initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
|
|
90
90
|
const taxonomyStateFingerprint = useMemo(() => buildTaxonomyStateFingerprint(taxonomyState), [taxonomyState]);
|
|
91
91
|
// === SECTION: State initialization ===
|
|
92
92
|
const [userGlobalSyncStatus, setUserGlobalSyncStatus] = useState('disconnected');
|
|
@@ -97,6 +97,7 @@ export function useSyncOrchestrator(input) {
|
|
|
97
97
|
const [workspaceLastErrorMessage, setWorkspaceLastErrorMessage] = useState(null);
|
|
98
98
|
const [workspaceCloudSyncEnabled, setWorkspaceCloudSyncEnabled] = useState(false);
|
|
99
99
|
const [workspaceSyncPhase, setWorkspaceSyncPhase] = useState('idle');
|
|
100
|
+
const [workspaceSyncSetupIntent, setWorkspaceSyncSetupIntent] = useState(null);
|
|
100
101
|
const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
|
|
101
102
|
const [workspaceSyncPendingChanges, setWorkspaceSyncPendingChanges] = useState(0);
|
|
102
103
|
const [workspaceSyncMarkdownDocuments, setWorkspaceSyncMarkdownDocuments] = useState([]);
|
|
@@ -253,14 +254,25 @@ export function useSyncOrchestrator(input) {
|
|
|
253
254
|
}));
|
|
254
255
|
}, []);
|
|
255
256
|
const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
|
|
256
|
-
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
257
|
+
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
257
258
|
currentWorkspaceId,
|
|
258
259
|
setWorkspaceCloudSyncEnabled,
|
|
259
260
|
setWorkspaceSyncPhase,
|
|
261
|
+
setWorkspaceSyncSetupIntent,
|
|
260
262
|
setWorkspaceLastPullAt,
|
|
261
263
|
setWorkspaceLastPushAt,
|
|
262
264
|
setWorkspaceLastErrorMessage
|
|
263
265
|
});
|
|
266
|
+
const applyWorkspaceSyncStateSnapshot = useCallback((nextState) => {
|
|
267
|
+
if (nextState.phase === 'attach-cloud' || nextState.phase === 'provision-local') {
|
|
268
|
+
lastBootstrapPhaseRef.current = nextState.phase;
|
|
269
|
+
if (bootstrapPhaseStartedAtRef.current === null) {
|
|
270
|
+
bootstrapPhaseStartedAtRef.current = Date.now();
|
|
271
|
+
}
|
|
272
|
+
bootstrapLastProgressAtRef.current = null;
|
|
273
|
+
}
|
|
274
|
+
applyWorkspaceSyncStateSnapshotBase(nextState);
|
|
275
|
+
}, [applyWorkspaceSyncStateSnapshotBase]);
|
|
264
276
|
const workspaceLastPushedWatermarksRef = useRef(new Map());
|
|
265
277
|
const workspaceLastPushedInitiativeIdsRef = useRef(new Set());
|
|
266
278
|
const workspaceLastPushedInitiativeWatermarksRef = useRef(new Map());
|
|
@@ -286,6 +298,7 @@ export function useSyncOrchestrator(input) {
|
|
|
286
298
|
const workspaceCaptureAttachBootstrapBaselineRef = useRef(false);
|
|
287
299
|
const workspaceDocumentsRefreshIntervalMs = 30_000;
|
|
288
300
|
const workspacePullIntervalMs = realtimeSyncEnabled ? 30_000 : 15_000;
|
|
301
|
+
const workspacePushWatchdogIntervalMs = Math.max(30_000, workspacePullIntervalMs);
|
|
289
302
|
const workspaceLastSuccessfulSyncAt = useMemo(() => {
|
|
290
303
|
const pullMs = workspaceLastPullAt ? Date.parse(workspaceLastPullAt) : NaN;
|
|
291
304
|
const pushMs = workspaceLastPushAt ? Date.parse(workspaceLastPushAt) : NaN;
|
|
@@ -560,7 +573,8 @@ export function useSyncOrchestrator(input) {
|
|
|
560
573
|
isAuthenticated,
|
|
561
574
|
resolveCloudAuthUrl,
|
|
562
575
|
workspaceId: currentWorkspaceId,
|
|
563
|
-
workspaceName: projectName || currentWorkspaceId
|
|
576
|
+
workspaceName: projectName || currentWorkspaceId,
|
|
577
|
+
allowProvisionOnMismatch: workspaceSyncPhase !== 'attach-cloud'
|
|
564
578
|
});
|
|
565
579
|
}, [
|
|
566
580
|
cloudAuthConfigured,
|
|
@@ -568,7 +582,8 @@ export function useSyncOrchestrator(input) {
|
|
|
568
582
|
isAuthenticated,
|
|
569
583
|
resolveCloudAuthUrl,
|
|
570
584
|
currentWorkspaceId,
|
|
571
|
-
projectName
|
|
585
|
+
projectName,
|
|
586
|
+
workspaceSyncPhase
|
|
572
587
|
]);
|
|
573
588
|
useEffect(() => {
|
|
574
589
|
workspaceTasksRef.current = tasks || [];
|
|
@@ -576,6 +591,14 @@ export function useSyncOrchestrator(input) {
|
|
|
576
591
|
useEffect(() => {
|
|
577
592
|
workspaceArchivedTasksRef.current = archivedTasks || [];
|
|
578
593
|
}, [archivedTasks]);
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
workspaceDeletedTaskWatermarksRef.current = new Map((deletedTasks || [])
|
|
596
|
+
.map((entry) => [
|
|
597
|
+
String(entry?.taskId || '').trim(),
|
|
598
|
+
String(entry?.deletedAt || '').trim()
|
|
599
|
+
])
|
|
600
|
+
.filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
|
|
601
|
+
}, [deletedTasks, workspaceDeletedTaskWatermarksRef]);
|
|
579
602
|
useEffect(() => {
|
|
580
603
|
workspaceInitiativesRef.current = initiatives || [];
|
|
581
604
|
}, [initiatives]);
|
|
@@ -595,6 +618,7 @@ export function useSyncOrchestrator(input) {
|
|
|
595
618
|
taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
|
|
596
619
|
lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
|
|
597
620
|
pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
|
|
621
|
+
pendingDeletedTaskWatermarks: workspaceDeletedTaskWatermarksRef.current,
|
|
598
622
|
initiatives: workspaceInitiativesRef.current,
|
|
599
623
|
workstreams: workspaceWorkstreamsRef.current,
|
|
600
624
|
taxonomies: taxonomies || [],
|
|
@@ -1175,6 +1199,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1175
1199
|
workspacePendingSignatureRef,
|
|
1176
1200
|
workspaceLastPushedTaskIdsRef,
|
|
1177
1201
|
workspaceDeletedTaskIdsRef,
|
|
1202
|
+
workspaceDeletedTaskWatermarksRef,
|
|
1178
1203
|
workspacePullCursorRef,
|
|
1179
1204
|
workspaceLastPushedWatermarksRef,
|
|
1180
1205
|
workspaceLastPushedInitiativeIdsRef,
|
|
@@ -1561,6 +1586,41 @@ export function useSyncOrchestrator(input) {
|
|
|
1561
1586
|
workspacePushInFlightRef,
|
|
1562
1587
|
workspacePullInFlightRef
|
|
1563
1588
|
]);
|
|
1589
|
+
useEffect(() => {
|
|
1590
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
1591
|
+
return;
|
|
1592
|
+
if (workspaceSyncPhase !== 'active')
|
|
1593
|
+
return;
|
|
1594
|
+
if (!workspaceSyncReferenceSnapshotsReady)
|
|
1595
|
+
return;
|
|
1596
|
+
const intervalId = window.setInterval(() => {
|
|
1597
|
+
if (workspacePushInFlightRef.current || workspacePullInFlightRef.current)
|
|
1598
|
+
return;
|
|
1599
|
+
if (isWorkspaceRetryPending())
|
|
1600
|
+
return;
|
|
1601
|
+
const signature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
1602
|
+
if (!signature || signature === workspaceLastPushedSignatureRef.current)
|
|
1603
|
+
return;
|
|
1604
|
+
const pendingPayload = buildWorkspaceSyncPayload();
|
|
1605
|
+
setWorkspaceSyncPendingChanges(pendingPayload.changes.length);
|
|
1606
|
+
if (pendingPayload.changes.length === 0)
|
|
1607
|
+
return;
|
|
1608
|
+
void pushWorkspaceChangesToCloud(signature);
|
|
1609
|
+
}, workspacePushWatchdogIntervalMs);
|
|
1610
|
+
return () => window.clearInterval(intervalId);
|
|
1611
|
+
}, [
|
|
1612
|
+
cloudAuthConfigured,
|
|
1613
|
+
runtimeMode,
|
|
1614
|
+
workspaceCloudSyncEnabled,
|
|
1615
|
+
authSessionResolved,
|
|
1616
|
+
workspaceSyncPhase,
|
|
1617
|
+
workspaceSyncReferenceSnapshotsReady,
|
|
1618
|
+
buildWorkspaceSyncSignature,
|
|
1619
|
+
buildWorkspaceSyncPayload,
|
|
1620
|
+
pushWorkspaceChangesToCloud,
|
|
1621
|
+
isWorkspaceRetryPending,
|
|
1622
|
+
workspacePushWatchdogIntervalMs
|
|
1623
|
+
]);
|
|
1564
1624
|
useEffect(() => {
|
|
1565
1625
|
if (cloudAuthConfigured && runtimeMode === 'local' && workspaceCloudSyncEnabled)
|
|
1566
1626
|
return;
|
|
@@ -1575,21 +1635,27 @@ export function useSyncOrchestrator(input) {
|
|
|
1575
1635
|
useEffect(() => {
|
|
1576
1636
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
1577
1637
|
return;
|
|
1578
|
-
if (workspaceSyncPhase === 'provision-local')
|
|
1638
|
+
if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'error')
|
|
1579
1639
|
return;
|
|
1580
1640
|
if (isWorkspaceRetryPending())
|
|
1581
1641
|
return;
|
|
1582
|
-
|
|
1642
|
+
const shouldKickOffPull = workspaceSyncPhase === 'attach-cloud' || !workspaceLastPullAt;
|
|
1643
|
+
if (shouldKickOffPull) {
|
|
1644
|
+
void pullWorkspaceChangesFromCloud();
|
|
1645
|
+
}
|
|
1583
1646
|
const intervalId = window.setInterval(() => {
|
|
1584
1647
|
void pullWorkspaceChangesFromCloud();
|
|
1585
1648
|
}, workspacePullIntervalMs);
|
|
1586
|
-
return () =>
|
|
1649
|
+
return () => {
|
|
1650
|
+
window.clearInterval(intervalId);
|
|
1651
|
+
};
|
|
1587
1652
|
}, [
|
|
1588
1653
|
cloudAuthConfigured,
|
|
1589
1654
|
runtimeMode,
|
|
1590
1655
|
workspaceCloudSyncEnabled,
|
|
1591
1656
|
authSessionResolved,
|
|
1592
1657
|
workspaceSyncPhase,
|
|
1658
|
+
workspaceLastPullAt,
|
|
1593
1659
|
isWorkspaceRetryPending,
|
|
1594
1660
|
pullWorkspaceChangesFromCloud,
|
|
1595
1661
|
workspacePullIntervalMs
|
|
@@ -1718,6 +1784,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1718
1784
|
workspaceLastSuccessfulSyncAt,
|
|
1719
1785
|
workspaceCloudSyncEnabled,
|
|
1720
1786
|
workspaceSyncPhase,
|
|
1787
|
+
workspaceSyncSetupIntent,
|
|
1721
1788
|
workspaceSyncStatus,
|
|
1722
1789
|
workspaceSyncSummary,
|
|
1723
1790
|
workspaceSyncRecommendedAction,
|
|
@@ -1732,6 +1799,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1732
1799
|
workspacePendingSignatureRef,
|
|
1733
1800
|
workspaceLastPushedTaskIdsRef,
|
|
1734
1801
|
workspaceDeletedTaskIdsRef,
|
|
1802
|
+
workspaceDeletedTaskWatermarksRef,
|
|
1735
1803
|
workspacePullCursorRef,
|
|
1736
1804
|
clearWorkspaceRetry,
|
|
1737
1805
|
persistWorkspaceSyncPatch,
|