@taskforcehq/taskforce 0.3.303 → 0.3.304

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 (165) hide show
  1. package/dist/Taskforce.module.css +102 -5
  2. package/dist/TaskforceCore.js +514 -212
  3. package/dist/TaskforceCore.test.js +1172 -133
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  21. package/dist/components/task/TaskStatusActions.js +8 -3
  22. package/dist/components/task/TaskStatusActions.test.js +9 -0
  23. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  24. package/dist/components/task/taskKanbanDropRules.js +187 -0
  25. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  26. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  27. package/dist/components/ui/Modal.d.ts +1 -1
  28. package/dist/components/ui/Modal.js +1 -0
  29. package/dist/components/views/AppShellHeader.js +5 -1
  30. package/dist/components/views/AppShellHeader.test.js +4 -0
  31. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  32. package/dist/components/views/PlanComparisonPage.js +4 -2
  33. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  34. package/dist/components/views/PlansPage.d.ts +5 -1
  35. package/dist/components/views/PlansPage.js +39 -12
  36. package/dist/components/views/PlansPage.test.js +59 -2
  37. package/dist/components/views/StandaloneLayout.js +113 -38
  38. package/dist/components/views/WidgetView.js +11 -2
  39. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  40. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  41. package/dist/core/PlanEntitlementService.d.ts +3 -1
  42. package/dist/core/PlanEntitlementService.js +61 -11
  43. package/dist/core/SignupMonetizationSettings.test.js +37 -2
  44. package/dist/core/TaskActivityService.d.ts +4 -1
  45. package/dist/core/TaskActivityService.js +45 -0
  46. package/dist/core/TaskAuditTimeline.test.js +52 -1
  47. package/dist/core/Taskforce.d.ts +15 -11
  48. package/dist/core/Taskforce.js +43 -2
  49. package/dist/core/WorkspacePlanMode.test.js +65 -0
  50. package/dist/documentReviews/service.d.ts +1 -1
  51. package/dist/documentReviews/types.d.ts +1 -1
  52. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  53. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  54. package/dist/hooks/sync/auth.d.ts +37 -0
  55. package/dist/hooks/sync/auth.js +61 -0
  56. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  57. package/dist/hooks/sync/bootstrap.js +52 -0
  58. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  59. package/dist/hooks/sync/orchestratorShared.js +209 -0
  60. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  61. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  62. package/dist/hooks/sync/realtime.d.ts +25 -0
  63. package/dist/hooks/sync/realtime.js +60 -0
  64. package/dist/hooks/sync/recovery.d.ts +69 -0
  65. package/dist/hooks/sync/recovery.js +118 -0
  66. package/dist/hooks/sync/transfers.d.ts +122 -0
  67. package/dist/hooks/sync/transfers.js +431 -0
  68. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  69. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  70. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  71. package/dist/hooks/ui/useResourceExport.js +5 -2
  72. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  73. package/dist/hooks/useRealtimeSync.js +2 -1
  74. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  75. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  76. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  77. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  78. package/dist/hooks/useSyncOrchestrator.js +314 -873
  79. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
  80. package/dist/hooks/useTaskMutations.js +45 -6
  81. package/dist/hooks/useTaskMutations.test.js +147 -0
  82. package/dist/hooks/useTaskforce.d.ts +8 -2
  83. package/dist/hooks/useTaskforce.js +285 -812
  84. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  85. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  86. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  87. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  88. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  89. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  90. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  91. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  92. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  93. package/dist/localization/locales/en-US.js +2 -2
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  95. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  96. package/dist/mcp/clientIntegrations.d.ts +12 -1
  97. package/dist/mcp/clientIntegrations.js +161 -6
  98. package/dist/mcp/clientIntegrations.test.js +76 -4
  99. package/dist/mcp/documentHelpers.d.ts +100 -0
  100. package/dist/mcp/documentHelpers.js +161 -0
  101. package/dist/mcp/runtime.js +174 -476
  102. package/dist/mcp/runtime.test.js +81 -8
  103. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  104. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  105. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  106. package/dist/mcp/taskAttachmentTypes.js +1 -0
  107. package/dist/plugins/vite.js +1 -1
  108. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  109. package/dist/runtime/appGateDefinitions.js +1 -1
  110. package/dist/server/index.d.ts +1 -0
  111. package/dist/server/index.js +46 -3
  112. package/dist/server/index.test.js +12 -1
  113. package/dist/server/routes/admin.js +13 -19
  114. package/dist/server/routes/auth.js +37 -2
  115. package/dist/server/routes/billing.js +18 -67
  116. package/dist/server/routes/billing.test.js +43 -0
  117. package/dist/server/routes/shared.d.ts +30 -0
  118. package/dist/server/routes/shared.js +26 -0
  119. package/dist/server/routes/sync.integration.test.js +20 -0
  120. package/dist/server/routes/sync.js +110 -237
  121. package/dist/server/routes/tasks.js +49 -7
  122. package/dist/server/routes.js +8 -19
  123. package/dist/server/routes.test.js +243 -5
  124. package/dist/shared/taskActor.d.ts +10 -0
  125. package/dist/shared/taskActor.js +1 -0
  126. package/dist/sync/collaborationSyncPayload.js +4 -0
  127. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  128. package/dist/sync/collaborationSyncState.d.ts +3 -1
  129. package/dist/sync/collaborationSyncState.js +19 -0
  130. package/dist/sync/contentSyncState.js +12 -10
  131. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  132. package/dist/sync/syncApplyHandlers.js +370 -8
  133. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  134. package/dist/sync/syncApplyHandlers.test.js +377 -0
  135. package/dist/sync/syncService.d.ts +8 -1
  136. package/dist/sync/syncService.js +36 -2
  137. package/dist/sync/syncService.test.js +48 -0
  138. package/dist/sync/workspacePullFeed.d.ts +1 -0
  139. package/dist/sync/workspacePullFeed.js +66 -59
  140. package/dist/sync/workspacePullFeed.test.js +72 -4
  141. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  142. package/dist/sync/workspaceSyncModel.js +65 -0
  143. package/dist/sync/workspaceSyncModel.test.js +119 -0
  144. package/dist/types.d.ts +2 -10
  145. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  146. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  147. package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  148. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
  149. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  150. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  151. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  152. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
  153. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  154. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  155. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
  156. package/dist/ui/index.html +4 -4
  157. package/dist/utils/taskActivity.js +4 -0
  158. package/dist/utils/taskActivity.test.js +25 -1
  159. package/package.json +3 -1
  160. package/scripts/playwright-auth.sh +4 -0
  161. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  162. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  163. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  164. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  165. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -92,7 +92,7 @@ describe('TaskforceCore task audit timeline', () => {
92
92
  cleanup();
93
93
  }
94
94
  });
95
- it('suppresses task events for autosave-style updates when skipTaskEvents is true', () => {
95
+ it('suppresses task events when skipTaskEvents is explicitly true', () => {
96
96
  const { core, cleanup } = makeCore();
97
97
  try {
98
98
  const task = core.createTask({ title: 'Autosave task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
@@ -110,6 +110,57 @@ describe('TaskforceCore task audit timeline', () => {
110
110
  cleanup();
111
111
  }
112
112
  });
113
+ it('suppresses title and description autosave events while preserving the saved task content', () => {
114
+ const { core, cleanup } = makeCore();
115
+ try {
116
+ const task = core.createTask({ title: 'Autosave title task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
117
+ core.updateTask(task.id, {
118
+ title: 'Autosave title task updated',
119
+ description: 'Draft body'
120
+ }, 'workspace-a', {
121
+ actorRef: 'user',
122
+ saveSource: 'autosave'
123
+ });
124
+ const events = core.listTaskEvents(task.id, 'workspace-a');
125
+ const hydrated = core.getTask(task.id, 'workspace-a');
126
+ expect(events.map((event) => event.action)).toEqual(['task-created']);
127
+ expect(hydrated?.title).toBe('Autosave title task updated');
128
+ expect(hydrated?.description).toBe('Draft body');
129
+ expect(hydrated?.activity?.some((item) => item.type === 'event' && item.event.action === 'task-updated')).toBe(false);
130
+ }
131
+ finally {
132
+ cleanup();
133
+ }
134
+ });
135
+ it('keeps meaningful autosave updates in the activity feed while filtering noisy text changes', () => {
136
+ const { core, cleanup } = makeCore();
137
+ try {
138
+ const task = core.createTask({ title: 'Autosave assignee task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
139
+ core.updateTask(task.id, {
140
+ title: 'Autosave assignee task updated',
141
+ assignee: 'member-1'
142
+ }, 'workspace-a', {
143
+ actorRef: 'user',
144
+ saveSource: 'autosave'
145
+ });
146
+ const assignmentEvent = core.listTaskEvents(task.id, 'workspace-a').find((event) => event.action === 'task-updated' && event.details?.changes?.assignee);
147
+ expect(assignmentEvent).toEqual(expect.objectContaining({
148
+ action: 'task-updated',
149
+ details: {
150
+ changes: {
151
+ assignee: {
152
+ from: 'unassigned',
153
+ to: 'member-1',
154
+ },
155
+ },
156
+ },
157
+ }));
158
+ expect(assignmentEvent?.details?.changes?.title).toBeUndefined();
159
+ }
160
+ finally {
161
+ cleanup();
162
+ }
163
+ });
113
164
  it('captures assignment changes in the core task update event details', () => {
114
165
  const { core, cleanup } = makeCore();
115
166
  try {
@@ -3,6 +3,8 @@ import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
3
3
  import { type SyncEventInput } from './SyncReconciliationService.js';
4
4
  import { type FirstClassTaxonomyDisplayLabels, type WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
5
5
  import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
6
+ import type { TaskActor } from '../shared/taskActor.js';
7
+ export type { TaskActor } from '../shared/taskActor.js';
6
8
  export type { WorkspaceDeletedAssetRecord, WorkspaceDeleteSummary } from './types.js';
7
9
  import type { GlobalTaskforceSettings, UserGlobalSyncSettings, WorkspaceDeleteSummary } from './types.js';
8
10
  import type { AnnotatedAttachmentAnnotation, AnnotatedAttachmentPayload, AnnotatedAttachmentSession, AnnotatedAttachmentSessionRecord } from '../annotatedAttachments/types.js';
@@ -73,16 +75,6 @@ export interface DeletedTaskRecord {
73
75
  }
74
76
  export type TaskStatus = 'task' | 'on-hold' | 'in-progress' | 'review' | 'done' | 'cancelled';
75
77
  export type TaskAssignee = string;
76
- export interface TaskActor {
77
- id: string;
78
- kind: 'human' | 'ai';
79
- label: string;
80
- username: string;
81
- icon: string;
82
- color: string;
83
- isNamed: boolean;
84
- meta?: Record<string, unknown>;
85
- }
86
78
  export interface TaskItem {
87
79
  id: string;
88
80
  referenceNumber?: number | null;
@@ -755,6 +747,9 @@ export interface TaskforceSyncCapable {
755
747
  workspaceId?: string | null;
756
748
  includeDeleted?: boolean;
757
749
  }): AnnotatedAttachmentSessionRecord[];
750
+ listTaskEventsForSync(input: {
751
+ workspaceId?: string | null;
752
+ }): TaskEvent[];
758
753
  upsertAiProfileForSync(profile: AiProfileRecord): AiProfileRecord;
759
754
  replaceWorkspaceUsersForSync(input: {
760
755
  workspaceId: string;
@@ -765,6 +760,7 @@ export interface TaskforceSyncCapable {
765
760
  upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
766
761
  upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord): DocumentReviewCommentSyncRecord;
767
762
  upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
763
+ upsertTaskEventForSync(event: TaskEvent): TaskEvent;
768
764
  deleteDocumentReviewSessionForSync(input: {
769
765
  workspaceId?: string | null;
770
766
  sessionId: string;
@@ -847,6 +843,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
847
843
  private areTaskEventValuesEqual;
848
844
  private normalizeTaskEventValue;
849
845
  private buildTaskUpdateChanges;
846
+ private filterAutosaveTaskEventChanges;
850
847
  private normalizeTaxonomyToken;
851
848
  private normalizeStatusValue;
852
849
  private sanitizeAssignee;
@@ -893,16 +890,18 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
893
890
  effectiveAt?: string | null;
894
891
  effectiveUntil?: string | null;
895
892
  billingMirrorPlanId?: string | null;
896
- source: 'registration' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
893
+ source: 'registration' | 'invite-accept' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
897
894
  updatedBy: string;
898
895
  createdAt?: string | null;
899
896
  updatedAt?: string | null;
900
897
  }): import("./types.js").AccountEntitlementSnapshot;
901
898
  private resolveWorkspaceLimitForUser;
902
899
  private resolveRegistrationOnboardingOutcome;
900
+ private ensureDefaultOnboardingEntitlement;
903
901
  createUserBillingRecord(userId: string, options?: {
904
902
  entitlementState?: string;
905
903
  planId?: string | null;
904
+ effectiveUntil?: string | null;
906
905
  }): void;
907
906
  getUserEntitlement(userId: string): {
908
907
  entitlementState: string;
@@ -1268,6 +1267,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1268
1267
  upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
1269
1268
  upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord): DocumentReviewCommentSyncRecord;
1270
1269
  upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
1270
+ upsertTaskEventForSync(event: TaskEvent): TaskEvent;
1271
1271
  deleteDocumentReviewSessionForSync(input: {
1272
1272
  workspaceId?: string | null;
1273
1273
  sessionId: string;
@@ -1668,6 +1668,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1668
1668
  updateWorkspaceUserRole(userId: string, workspaceId: string, role: AccessRole): boolean;
1669
1669
  getTask(id: string, workspaceId?: string): TaskItem | null;
1670
1670
  listTaskEvents(taskId: string, workspaceId?: string): TaskEvent[];
1671
+ listTaskEventsForSync(input: {
1672
+ workspaceId?: string | null;
1673
+ }): TaskEvent[];
1671
1674
  getScheduleBoard(weekKey: string, showWeekends?: boolean, workspaceId?: string): ScheduleBoardResult;
1672
1675
  updateTaskSchedule(id: string, input: {
1673
1676
  scheduledDate: string | null;
@@ -1699,6 +1702,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1699
1702
  syncUpdatedAt?: string;
1700
1703
  actorRef?: string;
1701
1704
  skipTaskEvents?: boolean;
1705
+ saveSource?: 'manual' | 'autosave';
1702
1706
  }): TaskItem | null;
1703
1707
  addComment(id: string, text: string, author: string, workspaceId?: string, options?: {
1704
1708
  persistBackup?: boolean;
@@ -9,7 +9,7 @@ import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
9
9
  import { AuthTokenService } from './AuthTokenService.js';
10
10
  import { getAiProfileByTokenService, listAiProfilesService, mergeAiProfilesService, resolveAiProfileService, upsertAiProfileForSyncService, } from './AiProfileService.js';
11
11
  import { syncCanonicalTaskAttachmentLinksService, } from './AttachmentLinkService.js';
12
- import { addTaskEventService, buildTaskActivityService, listTaskEventsInternalService, replaceTaskCommentsService, replaceTaskEventsService, } from './TaskActivityService.js';
12
+ import { addTaskEventService, buildTaskActivityService, listTaskEventsInternalService, listWorkspaceTaskEventsService, replaceTaskCommentsService, replaceTaskEventsService, upsertTaskEventForSyncService, } from './TaskActivityService.js';
13
13
  import { addDeletedTaskRecordService, getDeletedTaskService, listDeletedTasksService, mapDeletedTaskRowService, normalizeTaskSnapshotForRestoreService, pruneDeletedTasksService, purgeExpiredDeletedTasksService, } from './DeletedTaskLifecycleService.js';
14
14
  import { applyWorkspaceSyncSnapshotService, deleteDocumentWatermarkService, getDocumentWatermarkService, listTasksForSyncService, listWorkspaceSyncDeletesSinceService, readPushIdempotencyService, recordSyncEventService, upsertDocumentWatermarkService, writePushIdempotencyService, } from './SyncReconciliationService.js';
15
15
  import { getGlobalSettingsService, hasPersistedGlobalSettingsService, readGlobalConfigService, updateGlobalSettingsService, writeGlobalConfigService, } from './GlobalSettingsService.js';
@@ -666,6 +666,9 @@ export class TaskforceCore {
666
666
  }
667
667
  return changes;
668
668
  }
669
+ filterAutosaveTaskEventChanges(changes) {
670
+ return Object.fromEntries(Object.entries(changes).filter(([field]) => field !== 'title' && field !== 'description'));
671
+ }
669
672
  normalizeTaxonomyToken(value) {
670
673
  return String(value || '')
671
674
  .trim()
@@ -1217,6 +1220,29 @@ export class TaskforceCore {
1217
1220
  resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw) {
1218
1221
  return this.planEntitlementService.resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw);
1219
1222
  }
1223
+ ensureDefaultOnboardingEntitlement(accountIdRaw, options) {
1224
+ const accountId = String(accountIdRaw || '').trim();
1225
+ if (!accountId) {
1226
+ throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
1227
+ }
1228
+ if (this.hasAccountEntitlement(accountId)) {
1229
+ return this.getAccountEntitlement(accountId);
1230
+ }
1231
+ const now = options?.now || new Date().toISOString();
1232
+ const onboardingOutcome = this.resolveRegistrationOnboardingOutcome();
1233
+ this.transitionCanonicalCommercialState({
1234
+ accountId,
1235
+ planVersionId: onboardingOutcome.planVersionId,
1236
+ state: onboardingOutcome.commercialState,
1237
+ effectiveAt: now,
1238
+ billingMirrorPlanId: onboardingOutcome.planId,
1239
+ source: options?.source || 'registration',
1240
+ updatedBy: options?.updatedBy || 'registration',
1241
+ createdAt: now,
1242
+ updatedAt: now
1243
+ });
1244
+ return this.getAccountEntitlement(accountId);
1245
+ }
1220
1246
  createUserBillingRecord(userId, options) {
1221
1247
  return this.planEntitlementService.createUserBillingRecord(userId, options);
1222
1248
  }
@@ -4841,6 +4867,9 @@ export class TaskforceCore {
4841
4867
  deletedAt: session.deletedAt,
4842
4868
  });
4843
4869
  }
4870
+ upsertTaskEventForSync(event) {
4871
+ return upsertTaskEventForSyncService(this.getTaskActivityServiceDeps(), event);
4872
+ }
4844
4873
  deleteDocumentReviewSessionForSync(input) {
4845
4874
  const deps = this.getDocumentReviewServiceDeps();
4846
4875
  return deps.sessionStore.softDelete(input.sessionId, deps.normalizeWorkspaceId(input.workspaceId), input.deletedAt || undefined);
@@ -5795,6 +5824,11 @@ export class TaskforceCore {
5795
5824
  updated_at = ?
5796
5825
  WHERE tenant_id = ? AND id = ?
5797
5826
  `).run(hashPassword(password), 'password', user.email, now, now, this.tenantId, consumed.userId);
5827
+ this.ensureDefaultOnboardingEntitlement(consumed.userId, {
5828
+ source: 'invite-accept',
5829
+ updatedBy: 'invite-accept',
5830
+ now
5831
+ });
5798
5832
  return this.completeInviteWorkspaceActivation({
5799
5833
  userId: consumed.userId,
5800
5834
  workspaceId: inviteWorkspaceId
@@ -5953,6 +5987,10 @@ export class TaskforceCore {
5953
5987
  const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
5954
5988
  return this.listTaskEventsInternal(taskId, normalizedWorkspaceId);
5955
5989
  }
5990
+ listTaskEventsForSync(input) {
5991
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(input.workspaceId || undefined);
5992
+ return listWorkspaceTaskEventsService(this.getTaskActivityServiceDeps(), normalizedWorkspaceId);
5993
+ }
5956
5994
  getScheduleBoard(weekKey, showWeekends = false, workspaceId) {
5957
5995
  const parsedWeek = this.parseIsoWeekKeyToDates(weekKey);
5958
5996
  if (!parsedWeek) {
@@ -6510,7 +6548,10 @@ export class TaskforceCore {
6510
6548
  if (!nextTask)
6511
6549
  return null;
6512
6550
  if (options?.skipTaskEvents !== true) {
6513
- const changes = this.buildTaskUpdateChanges(existing, nextTask, patch);
6551
+ const rawChanges = this.buildTaskUpdateChanges(existing, nextTask, patch);
6552
+ const changes = options?.saveSource === 'autosave'
6553
+ ? this.filterAutosaveTaskEventChanges(rawChanges)
6554
+ : rawChanges;
6514
6555
  if (Object.keys(changes).length > 0) {
6515
6556
  let action = 'task-updated';
6516
6557
  if (changes.status) {
@@ -23,6 +23,17 @@ function openCore(projectRoot) {
23
23
  core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter' });
24
24
  addFreeMapping('starter-v1');
25
25
  core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
26
+ core.updatePlanVersion({
27
+ planId: 'starter',
28
+ versionNumber: 1,
29
+ seatLimit: 1
30
+ });
31
+ core.updatePlanVersionFeatures({
32
+ planVersionId: 'starter-v1',
33
+ features: [
34
+ { featureKey: 'workspace.switching', access: 'enabled' }
35
+ ]
36
+ });
26
37
  core.upsertPlanCatalog({ planId: 'collab', displayName: 'Collab' });
27
38
  addFreeMapping('collab-v1');
28
39
  core.upsertPlanCatalog({ planId: 'collab', displayName: 'Collab', status: 'active' });
@@ -214,4 +225,58 @@ describe('TaskforceCore account-level plan mode and invite revoke', () => {
214
225
  fs.rmSync(projectRoot, { recursive: true, force: true });
215
226
  }
216
227
  });
228
+ it('grants the default onboarding entitlement to newly accepted invited accounts', () => {
229
+ const projectRoot = makeProjectRoot();
230
+ const core = openCore(projectRoot);
231
+ try {
232
+ core.updateGlobalSettings({
233
+ billing: {
234
+ onboardingPolicy: {
235
+ mode: 'auto_grant_free',
236
+ planVersionId: 'starter-v1'
237
+ }
238
+ }
239
+ });
240
+ core.createWorkspace({ workspaceId: 'workspace-invite', name: 'Invite Workspace' });
241
+ core.ensureBootstrapUserAccess({
242
+ userId: 'owner-user',
243
+ workspaceId: 'workspace-invite',
244
+ role: 'owner',
245
+ email: 'owner@example.com'
246
+ });
247
+ core.setUserAccountPlanMode('owner-user', 'team');
248
+ core.ensureBootstrapUserAccess({
249
+ userId: 'invited-user',
250
+ workspaceId: 'workspace-invite',
251
+ role: 'member',
252
+ email: 'invited@example.com'
253
+ });
254
+ core.setWorkspaceUserDisabled('invited-user', 'workspace-invite', true);
255
+ const db = core.db;
256
+ db.prepare(`
257
+ UPDATE workspace_memberships
258
+ SET status = 'invited'
259
+ WHERE tenant_id = ? AND user_id = ? AND workspace_id = ?
260
+ `).run('default', 'invited-user', 'workspace-invite');
261
+ const invite = core.requestInviteAcceptance({ userId: 'invited-user', workspaceId: 'workspace-invite' });
262
+ const accepted = core.acceptInviteWithToken({
263
+ token: String(invite.token),
264
+ password: 'Password123!'
265
+ });
266
+ expect(accepted.workspaceId).toBe('workspace-invite');
267
+ expect(core.hasAccountEntitlement('invited-user')).toBe(true);
268
+ expect(core.getAccountEntitlement('invited-user')).toEqual(expect.objectContaining({
269
+ planVersionId: 'starter-v1',
270
+ state: 'active'
271
+ }));
272
+ expect(core.getAccountAccessStatus('invited-user')).toEqual(expect.objectContaining({
273
+ gate: 'ok',
274
+ canAccessApp: true
275
+ }));
276
+ }
277
+ finally {
278
+ core.close();
279
+ fs.rmSync(projectRoot, { recursive: true, force: true });
280
+ }
281
+ });
217
282
  });
@@ -1,4 +1,4 @@
1
- import type { TaskActor } from '../core/Taskforce.js';
1
+ import type { TaskActor } from '../shared/taskActor.js';
2
2
  import type { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
3
3
  import { DocumentReviewCommentStore } from './DocumentReviewCommentStore.js';
4
4
  import { DocumentReviewSessionStore } from './DocumentReviewSessionStore.js';
@@ -1,4 +1,4 @@
1
- import type { TaskActor } from '../core/Taskforce.js';
1
+ import type { TaskActor } from '../shared/taskActor.js';
2
2
  export type DocumentReviewSessionStatus = 'open' | 'resolved';
3
3
  export type DocumentReviewAnchorKind = 'document' | 'quote' | 'line-range';
4
4
  export interface DocumentReviewDocumentAnchor {
@@ -0,0 +1,48 @@
1
+ import { type MutableRefObject } from 'react';
2
+ import { createDefaultAssigneeOptions } from '../../utils/assignees';
3
+ export type BootstrapPhase = 'idle' | 'auth' | 'workspace' | 'config' | 'ready' | 'stalled';
4
+ interface UseTaskforceAuthBootstrapArgs {
5
+ runtimeConfigReady: boolean;
6
+ shouldProbeCloudAuth: boolean;
7
+ authSessionResolved: boolean;
8
+ resolveCloudAuthUrl: (path: string) => string;
9
+ authOnlyCloudMode: boolean;
10
+ authRequiredError: string;
11
+ runtimeMode: 'local' | 'cloud';
12
+ workspaceSelectionScope: string;
13
+ markBootstrapPhase: (phase: Exclude<BootstrapPhase, 'stalled'>) => void;
14
+ markBootstrapStalled: (message: string) => void;
15
+ setRuntimeMode: (mode: 'local' | 'cloud') => void;
16
+ setAuthRequiredForApi: (value: boolean) => void;
17
+ setIsAuthenticated: (value: boolean) => void;
18
+ setAuthUserId: (value: string) => void;
19
+ setAuthWorkspaceId: (value: string) => void;
20
+ setAuthUserEmail: (value: string) => void;
21
+ setAuthUserDisplayName: (value: string) => void;
22
+ setAuthUserAvatarUrl: (value: string) => void;
23
+ setUserGlobalSyncStatus: (value: 'idle' | 'disconnected') => void;
24
+ setUserGlobalSyncError: (value: string | null) => void;
25
+ setHasBetaAccess: (value: boolean) => void;
26
+ setAvailableWorkspaces: (value: any[]) => void;
27
+ setHydratedWorkspaceRole: (value: any) => void;
28
+ setAssigneeOptions: (value: ReturnType<typeof createDefaultAssigneeOptions>) => void;
29
+ setAuthBlocked: (value: boolean) => void;
30
+ setAuthSessionResolved: (value: boolean) => void;
31
+ setError: (value: string | ((prev: string) => string)) => void;
32
+ applyResolvedWorkspaceId: (workspaceId: string, options?: {
33
+ preserveNonDefaultDefault?: boolean;
34
+ }) => void;
35
+ readPersistedWorkspaceId: (scope: string) => string;
36
+ authSessionRequestRef: MutableRefObject<Promise<boolean> | null>;
37
+ authSessionEpochRef: MutableRefObject<number>;
38
+ authSessionLastCheckedAtRef: MutableRefObject<number>;
39
+ authSessionLastResultRef: MutableRefObject<boolean>;
40
+ isLoopbackHost: boolean;
41
+ setAuthStateReadyRefs: (authenticated: boolean, authSessionResolved: boolean, authRequiredForApi: boolean) => void;
42
+ }
43
+ export declare function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloudAuth, authSessionResolved, resolveCloudAuthUrl, authOnlyCloudMode, authRequiredError, runtimeMode, workspaceSelectionScope, markBootstrapPhase, markBootstrapStalled, setRuntimeMode, setAuthRequiredForApi, setIsAuthenticated, setAuthUserId, setAuthWorkspaceId, setAuthUserEmail, setAuthUserDisplayName, setAuthUserAvatarUrl, setUserGlobalSyncStatus, setUserGlobalSyncError, setHasBetaAccess, setAvailableWorkspaces, setHydratedWorkspaceRole, setAssigneeOptions, setAuthBlocked, setAuthSessionResolved, setError, applyResolvedWorkspaceId, readPersistedWorkspaceId, authSessionRequestRef, authSessionEpochRef, authSessionLastCheckedAtRef, authSessionLastResultRef, isLoopbackHost, setAuthStateReadyRefs }: UseTaskforceAuthBootstrapArgs): {
44
+ checkAuthSession: (options?: {
45
+ force?: boolean;
46
+ }) => Promise<boolean>;
47
+ };
48
+ export {};
@@ -0,0 +1,219 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { createDefaultAssigneeOptions } from '../../utils/assignees';
3
+ import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../../utils/taskforceApiClient';
4
+ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloudAuth, authSessionResolved, resolveCloudAuthUrl, authOnlyCloudMode, authRequiredError, runtimeMode, workspaceSelectionScope, markBootstrapPhase, markBootstrapStalled, setRuntimeMode, setAuthRequiredForApi, setIsAuthenticated, setAuthUserId, setAuthWorkspaceId, setAuthUserEmail, setAuthUserDisplayName, setAuthUserAvatarUrl, setUserGlobalSyncStatus, setUserGlobalSyncError, setHasBetaAccess, setAvailableWorkspaces, setHydratedWorkspaceRole, setAssigneeOptions, setAuthBlocked, setAuthSessionResolved, setError, applyResolvedWorkspaceId, readPersistedWorkspaceId, authSessionRequestRef, authSessionEpochRef, authSessionLastCheckedAtRef, authSessionLastResultRef, isLoopbackHost, setAuthStateReadyRefs }) {
5
+ const checkAuthSession = useCallback(async (options) => {
6
+ const force = options?.force === true;
7
+ markBootstrapPhase('auth');
8
+ if (!runtimeConfigReady) {
9
+ return authSessionLastResultRef.current;
10
+ }
11
+ if (!shouldProbeCloudAuth) {
12
+ setRuntimeMode('local');
13
+ setAuthRequiredForApi(false);
14
+ setIsAuthenticated(false);
15
+ setAuthUserId('anonymous');
16
+ setAuthWorkspaceId('');
17
+ persistLocalMutationActorUserId(null);
18
+ setAuthUserEmail('');
19
+ setAuthUserDisplayName('');
20
+ setAuthUserAvatarUrl('');
21
+ setUserGlobalSyncStatus('disconnected');
22
+ setUserGlobalSyncError(null);
23
+ setHasBetaAccess(true);
24
+ setAvailableWorkspaces([]);
25
+ setHydratedWorkspaceRole(null);
26
+ setAssigneeOptions(createDefaultAssigneeOptions());
27
+ setAuthBlocked(false);
28
+ setAuthSessionResolved(true);
29
+ setAuthStateReadyRefs(false, true, false);
30
+ authSessionLastResultRef.current = false;
31
+ authSessionLastCheckedAtRef.current = Date.now();
32
+ return false;
33
+ }
34
+ const sessionUrl = resolveCloudAuthUrl('/api/taskforce/auth/session');
35
+ const now = Date.now();
36
+ if (!force && authSessionRequestRef.current)
37
+ return authSessionRequestRef.current;
38
+ if (!force && authSessionResolved && (now - authSessionLastCheckedAtRef.current) < 1500) {
39
+ return authSessionLastResultRef.current;
40
+ }
41
+ const requestEpoch = ++authSessionEpochRef.current;
42
+ let request = null;
43
+ request = (async () => {
44
+ const abortController = new AbortController();
45
+ const timeoutId = typeof window !== 'undefined'
46
+ ? window.setTimeout(() => abortController.abort(), 8000)
47
+ : null;
48
+ const isStaleRequest = () => authSessionEpochRef.current !== requestEpoch;
49
+ try {
50
+ const res = await fetch(sessionUrl, {
51
+ method: 'GET',
52
+ credentials: 'include',
53
+ mode: 'cors',
54
+ signal: abortController.signal
55
+ });
56
+ if (isStaleRequest()) {
57
+ return authSessionLastResultRef.current;
58
+ }
59
+ if (res.status === 429) {
60
+ setAuthSessionResolved(true);
61
+ setAuthStateReadyRefs(authSessionLastResultRef.current, true, false);
62
+ return authSessionLastResultRef.current;
63
+ }
64
+ if (!res.ok) {
65
+ if (isStaleRequest()) {
66
+ return authSessionLastResultRef.current;
67
+ }
68
+ if (isLoopbackHost)
69
+ persistLocalMutationActorUserId(null);
70
+ setAuthSessionResolved(true);
71
+ setAuthStateReadyRefs(false, true, false);
72
+ authSessionLastResultRef.current = false;
73
+ return false;
74
+ }
75
+ const data = await res.json();
76
+ if (isStaleRequest()) {
77
+ return authSessionLastResultRef.current;
78
+ }
79
+ const required = Boolean(data.authRequiredForApi);
80
+ const authenticated = Boolean(data.authenticated);
81
+ const betaAccess = authenticated ? (data.betaAccess !== false) : true;
82
+ const workspaceId = typeof data.workspaceId === 'string' && data.workspaceId.trim().length > 0
83
+ ? data.workspaceId.trim()
84
+ : '';
85
+ const userId = typeof data.userId === 'string' && data.userId.trim().length > 0
86
+ ? data.userId.trim()
87
+ : 'anonymous';
88
+ const userEmail = typeof data.email === 'string' && data.email.trim().length > 0
89
+ ? data.email.trim().toLowerCase()
90
+ : '';
91
+ const userDisplayName = typeof data.displayName === 'string' ? data.displayName.trim() : '';
92
+ const userAvatarUrl = typeof data.avatarUrl === 'string' ? data.avatarUrl.trim() : '';
93
+ const isLocalRuntime = runtimeMode === 'local';
94
+ const keepLocalWorkspaceContext = runtimeMode === 'local' || authOnlyCloudMode;
95
+ const nextAuthRequiredForApi = authOnlyCloudMode ? false : (isLocalRuntime ? false : required);
96
+ setAuthRequiredForApi(nextAuthRequiredForApi);
97
+ setIsAuthenticated(authenticated);
98
+ setAuthUserId(authenticated ? userId : 'anonymous');
99
+ setAuthWorkspaceId(authenticated ? workspaceId : '');
100
+ if (isLoopbackHost)
101
+ persistLocalMutationActorUserId(authenticated ? userId : null);
102
+ setAuthUserEmail(authenticated ? userEmail : '');
103
+ setAuthUserDisplayName(authenticated ? userDisplayName : '');
104
+ setAuthUserAvatarUrl(authenticated ? userAvatarUrl : '');
105
+ if (authenticated) {
106
+ setError((prev) => prev === authRequiredError ? '' : prev);
107
+ }
108
+ setUserGlobalSyncStatus(authenticated ? 'idle' : 'disconnected');
109
+ setUserGlobalSyncError(null);
110
+ setHasBetaAccess(betaAccess);
111
+ if (!keepLocalWorkspaceContext) {
112
+ applyResolvedWorkspaceId(workspaceId || readPersistedWorkspaceId(workspaceSelectionScope) || 'default', { preserveNonDefaultDefault: true });
113
+ }
114
+ setAuthBlocked(authOnlyCloudMode ? false : (isLocalRuntime ? false : (required && !authenticated)));
115
+ setAuthSessionResolved(true);
116
+ setAuthStateReadyRefs(authenticated, true, nextAuthRequiredForApi);
117
+ authSessionLastResultRef.current = authenticated;
118
+ if (authenticated)
119
+ markBootstrapPhase('ready');
120
+ return authenticated;
121
+ }
122
+ catch {
123
+ if (isStaleRequest()) {
124
+ return authSessionLastResultRef.current;
125
+ }
126
+ if (isLoopbackHost)
127
+ persistLocalMutationActorUserId(null);
128
+ setAuthSessionResolved(true);
129
+ setAuthStateReadyRefs(false, true, false);
130
+ authSessionLastResultRef.current = false;
131
+ markBootstrapStalled('Unable to verify your session.');
132
+ return authSessionLastResultRef.current;
133
+ }
134
+ finally {
135
+ if (timeoutId !== null && typeof window !== 'undefined') {
136
+ window.clearTimeout(timeoutId);
137
+ }
138
+ if (!isStaleRequest()) {
139
+ authSessionLastCheckedAtRef.current = Date.now();
140
+ }
141
+ if (request && authSessionRequestRef.current === request) {
142
+ authSessionRequestRef.current = null;
143
+ }
144
+ }
145
+ })();
146
+ authSessionRequestRef.current = request;
147
+ return request;
148
+ }, [
149
+ runtimeConfigReady,
150
+ shouldProbeCloudAuth,
151
+ authSessionResolved,
152
+ resolveCloudAuthUrl,
153
+ authOnlyCloudMode,
154
+ authRequiredError,
155
+ runtimeMode,
156
+ workspaceSelectionScope,
157
+ markBootstrapPhase,
158
+ markBootstrapStalled,
159
+ setRuntimeMode,
160
+ setAuthRequiredForApi,
161
+ setIsAuthenticated,
162
+ setAuthUserId,
163
+ setAuthWorkspaceId,
164
+ setAuthUserEmail,
165
+ setAuthUserDisplayName,
166
+ setAuthUserAvatarUrl,
167
+ setUserGlobalSyncStatus,
168
+ setUserGlobalSyncError,
169
+ setHasBetaAccess,
170
+ setAvailableWorkspaces,
171
+ setHydratedWorkspaceRole,
172
+ setAssigneeOptions,
173
+ setAuthBlocked,
174
+ setAuthSessionResolved,
175
+ setError,
176
+ applyResolvedWorkspaceId,
177
+ readPersistedWorkspaceId,
178
+ authSessionRequestRef,
179
+ authSessionEpochRef,
180
+ authSessionLastCheckedAtRef,
181
+ authSessionLastResultRef,
182
+ isLoopbackHost,
183
+ setAuthStateReadyRefs
184
+ ]);
185
+ const optimisticLocalAuthUserId = runtimeMode === 'local' && isLoopbackHost && shouldProbeCloudAuth
186
+ ? readLocalMutationActorUserId()
187
+ : '';
188
+ useEffect(() => {
189
+ if (optimisticLocalAuthUserId) {
190
+ setIsAuthenticated(true);
191
+ setAuthSessionResolved(true);
192
+ setAuthUserId(optimisticLocalAuthUserId);
193
+ setAuthStateReadyRefs(true, true, false);
194
+ authSessionLastResultRef.current = true;
195
+ }
196
+ }, [optimisticLocalAuthUserId, setAuthSessionResolved, setAuthStateReadyRefs, setAuthUserId, setIsAuthenticated, authSessionLastResultRef]);
197
+ useEffect(() => {
198
+ if (runtimeMode !== 'local' || !shouldProbeCloudAuth)
199
+ return;
200
+ if (typeof window === 'undefined')
201
+ return;
202
+ const recheck = () => { void checkAuthSession(); };
203
+ const onVisibilityChange = () => {
204
+ if (document.visibilityState === 'visible')
205
+ recheck();
206
+ };
207
+ window.addEventListener('focus', recheck);
208
+ window.addEventListener('online', recheck);
209
+ document.addEventListener('visibilitychange', onVisibilityChange);
210
+ const intervalId = window.setInterval(recheck, 30_000);
211
+ return () => {
212
+ window.removeEventListener('focus', recheck);
213
+ window.removeEventListener('online', recheck);
214
+ document.removeEventListener('visibilitychange', onVisibilityChange);
215
+ window.clearInterval(intervalId);
216
+ };
217
+ }, [runtimeMode, shouldProbeCloudAuth, checkAuthSession]);
218
+ return { checkAuthSession };
219
+ }
@@ -0,0 +1,37 @@
1
+ import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
2
+ interface CreateSyncAuthCoordinatorArgs {
3
+ currentWorkspaceId: string;
4
+ workspaceSyncPhase: WorkspaceSyncPhase;
5
+ clearWorkspaceRetry: () => void;
6
+ setWorkspaceSyncBusy: (value: boolean) => void;
7
+ setUserGlobalSyncStatus: (value: 'error') => void;
8
+ setUserGlobalSyncError: (value: string) => void;
9
+ setWorkspaceLastErrorMessage: (value: string) => void;
10
+ setWorkspaceLastErrorAt: (value: string) => void;
11
+ reportSyncEvent: (workspaceId: string, event: {
12
+ eventType: 'pull' | 'handshake' | 'bootstrap' | 'apply' | 'push';
13
+ status: 'success' | 'error';
14
+ statusCode?: number;
15
+ errorMessage?: string;
16
+ }) => void;
17
+ setAuthBlocked: (value: boolean) => void;
18
+ setIsAuthenticated: (value: boolean) => void;
19
+ checkAuthSession: () => Promise<boolean>;
20
+ ensureCloudWorkspaceReadyForSync: () => Promise<{
21
+ success: boolean;
22
+ statusCode?: number;
23
+ error?: string;
24
+ transient?: boolean;
25
+ retryAfterMs?: number;
26
+ }>;
27
+ scheduleWorkspaceSyncRetry: (minDelayMs?: number) => void;
28
+ persistWorkspaceSyncPatchSafely: (patch: {
29
+ phase: 'error';
30
+ lastErrorMessage: string;
31
+ }) => void;
32
+ }
33
+ export declare function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPhase, clearWorkspaceRetry, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, reportSyncEvent, setAuthBlocked, setIsAuthenticated, checkAuthSession, ensureCloudWorkspaceReadyForSync, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely }: CreateSyncAuthCoordinatorArgs): {
34
+ handleSyncAuthFailure: (source: "push" | "pull" | "apply" | "handshake", statusCode: number) => Promise<void>;
35
+ ensureWorkspaceSyncReady: () => Promise<boolean>;
36
+ };
37
+ export {};