@taskforcehq/taskforce 0.3.302 → 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 (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  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/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. package/dist/utils/billingStatusCache.js +0 -70
@@ -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
  });
@@ -268,8 +268,9 @@ export interface GlobalTaskforceSettings {
268
268
  authRateLimitEnabled?: boolean;
269
269
  authRateLimitMaxRequests?: number;
270
270
  authRateLimitWindowMs?: number;
271
- signupMonetizationStrategy?: 'auto_assign_default' | 'plan_selection_required';
272
- defaultSignupPlanVersionId?: string | null;
271
+ };
272
+ billing?: {
273
+ onboardingPolicy?: OnboardingPolicy;
273
274
  };
274
275
  site?: {
275
276
  showRunTaskforceLocally?: boolean;
@@ -299,14 +300,14 @@ export interface UserGlobalSyncSettings {
299
300
  weekStartsOn?: 'sunday' | 'monday';
300
301
  };
301
302
  }
302
- export type SignupMonetizationStrategy = 'auto_assign_default' | 'plan_selection_required';
303
- export interface SignupMonetizationSettings {
304
- strategy: SignupMonetizationStrategy;
305
- defaultSignupPlanVersionId: string | null;
303
+ export type OnboardingPolicyMode = 'require_plan_selection' | 'auto_grant_free' | 'auto_start_trial';
304
+ export interface OnboardingPolicy {
305
+ mode: OnboardingPolicyMode;
306
+ planVersionId: string | null;
306
307
  }
307
- export interface SignupMonetizationValidationResult {
308
+ export interface OnboardingPolicyValidationResult {
308
309
  valid: boolean;
309
- code?: 'SIGNUP_PLAN_VERSION_REQUIRED' | 'SIGNUP_PLAN_VERSION_NOT_FOUND' | 'SIGNUP_PLAN_NOT_ENABLED';
310
+ code?: 'ONBOARDING_PLAN_VERSION_REQUIRED' | 'ONBOARDING_PLAN_VERSION_NOT_FOUND' | 'ONBOARDING_PLAN_NOT_ENABLED' | 'ONBOARDING_PLAN_MUST_BE_FREE' | 'ONBOARDING_PLAN_TRIAL_REQUIRED';
310
311
  message?: string;
311
312
  details?: Record<string, unknown>;
312
313
  }
@@ -441,7 +442,7 @@ export interface McpAuditLogRecord {
441
442
  reasonCode: string | null;
442
443
  details: Record<string, unknown>;
443
444
  }
444
- export type EntitlementState = 'active' | 'trialing' | 'grace' | 'suspended' | 'canceled';
445
+ export type EntitlementState = 'pending_plan_selection' | 'checkout_pending' | 'active' | 'trialing' | 'grace' | 'suspended' | 'canceled';
445
446
  export type PlanFeatureAccess = 'enabled' | 'disabled' | 'limited';
446
447
  export interface PlanFeatureSnapshot {
447
448
  featureKey: string;
@@ -499,17 +500,15 @@ export interface AccountAccessStatus_Ok {
499
500
  hasEntitlement: true;
500
501
  canAccessApp: true;
501
502
  canAccessPlans: true;
502
- signupMonetizationStrategy: SignupMonetizationStrategy;
503
503
  planSelectionRequired: false;
504
504
  message: null;
505
505
  entitlement: AccountEntitlementSnapshot;
506
506
  }
507
507
  export interface AccountAccessStatus_Blocked {
508
- gate: 'plan_selection_required' | 'misconfigured' | 'missing_entitlement';
508
+ gate: 'plan_selection_required' | 'checkout_pending' | 'misconfigured' | 'missing_entitlement';
509
509
  hasEntitlement: boolean;
510
510
  canAccessApp: false;
511
511
  canAccessPlans: true;
512
- signupMonetizationStrategy: SignupMonetizationStrategy;
513
512
  planSelectionRequired: boolean;
514
513
  message: string;
515
514
  entitlement: AccountEntitlementSnapshot | null;
@@ -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 {};
@@ -0,0 +1,61 @@
1
+ import { isTransientWorkspaceSyncStatus } from '../../sync/syncService';
2
+ import { isValidWorkspaceKey } from './orchestratorShared';
3
+ export function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPhase, clearWorkspaceRetry, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, reportSyncEvent, setAuthBlocked, setIsAuthenticated, checkAuthSession, ensureCloudWorkspaceReadyForSync, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely }) {
4
+ const handleSyncAuthFailure = async (source, statusCode) => {
5
+ clearWorkspaceRetry();
6
+ setWorkspaceSyncBusy(false);
7
+ setUserGlobalSyncStatus('error');
8
+ const message = 'Session expired. Sign in again to resume cloud sync.';
9
+ setUserGlobalSyncError(message);
10
+ setWorkspaceLastErrorMessage(message);
11
+ setWorkspaceLastErrorAt(new Date().toISOString());
12
+ reportSyncEvent(currentWorkspaceId, {
13
+ eventType: source === 'handshake' ? 'handshake' : source,
14
+ status: 'error',
15
+ statusCode,
16
+ errorMessage: message
17
+ });
18
+ setAuthBlocked(true);
19
+ setIsAuthenticated(false);
20
+ await checkAuthSession();
21
+ };
22
+ const ensureWorkspaceSyncReady = async () => {
23
+ if (!isValidWorkspaceKey(currentWorkspaceId)) {
24
+ const message = 'Workspace sync blocked: local workspace ID is unresolved.';
25
+ setWorkspaceLastErrorMessage(message);
26
+ setWorkspaceLastErrorAt(new Date().toISOString());
27
+ setUserGlobalSyncError(message);
28
+ setUserGlobalSyncStatus('error');
29
+ return false;
30
+ }
31
+ const ensured = await ensureCloudWorkspaceReadyForSync();
32
+ if (ensured.success)
33
+ return true;
34
+ if (ensured.statusCode === 401) {
35
+ await handleSyncAuthFailure('handshake', 401);
36
+ return false;
37
+ }
38
+ const message = ensured.error || 'Workspace sync handshake failed.';
39
+ setWorkspaceLastErrorMessage(message);
40
+ setWorkspaceLastErrorAt(new Date().toISOString());
41
+ setUserGlobalSyncError(message);
42
+ setUserGlobalSyncStatus('error');
43
+ reportSyncEvent(currentWorkspaceId, {
44
+ eventType: 'handshake',
45
+ status: 'error',
46
+ statusCode: ensured.statusCode,
47
+ errorMessage: message
48
+ });
49
+ if (ensured.transient || isTransientWorkspaceSyncStatus(ensured.statusCode)) {
50
+ scheduleWorkspaceSyncRetry(ensured.retryAfterMs);
51
+ }
52
+ else if (workspaceSyncPhase === 'active') {
53
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
54
+ }
55
+ return false;
56
+ };
57
+ return {
58
+ handleSyncAuthFailure,
59
+ ensureWorkspaceSyncReady
60
+ };
61
+ }
@@ -0,0 +1,34 @@
1
+ import type { MutableRefObject } from 'react';
2
+ import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
3
+ interface BootstrapPhaseTrackingArgs {
4
+ workspaceSyncPhase: WorkspaceSyncPhase;
5
+ lastBootstrapPhaseRef: MutableRefObject<WorkspaceSyncPhase>;
6
+ bootstrapPhaseStartedAtRef: MutableRefObject<number | null>;
7
+ bootstrapLastProgressAtRef: MutableRefObject<number | null>;
8
+ }
9
+ export declare function syncBootstrapPhaseTracking({ workspaceSyncPhase, lastBootstrapPhaseRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef }: BootstrapPhaseTrackingArgs): void;
10
+ interface BootstrapTimeoutMonitorArgs {
11
+ currentWorkspaceId: string;
12
+ workspaceCloudSyncEnabled: boolean;
13
+ workspaceSyncPhase: WorkspaceSyncPhase;
14
+ workspacePullInFlightRef: MutableRefObject<boolean>;
15
+ workspacePushInFlightRef: MutableRefObject<boolean>;
16
+ bootstrapPhaseStartedAtRef: MutableRefObject<number | null>;
17
+ bootstrapLastProgressAtRef: MutableRefObject<number | null>;
18
+ setWorkspaceLastErrorMessage: (value: string) => void;
19
+ setWorkspaceLastErrorAt: (value: string) => void;
20
+ setUserGlobalSyncStatus: (value: 'error') => void;
21
+ setUserGlobalSyncError: (value: string) => void;
22
+ setWorkspaceSyncBusy: (value: boolean) => void;
23
+ persistWorkspaceSyncPatchSafely: (patch: {
24
+ phase: 'error';
25
+ lastErrorMessage: string;
26
+ }) => void;
27
+ }
28
+ export declare function createBootstrapTimeoutMonitor({ currentWorkspaceId, workspaceCloudSyncEnabled, workspaceSyncPhase, workspacePullInFlightRef, workspacePushInFlightRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceSyncBusy, persistWorkspaceSyncPatchSafely }: BootstrapTimeoutMonitorArgs): (() => void) | undefined;
29
+ export declare function resolveRepairPhase(args: {
30
+ workspaceSyncPhase: WorkspaceSyncPhase;
31
+ lastBootstrapPhase: WorkspaceSyncPhase;
32
+ workspaceLastPullAt: string | null;
33
+ }): WorkspaceSyncPhase;
34
+ export {};
@@ -0,0 +1,52 @@
1
+ import { WORKSPACE_BOOTSTRAP_TIMEOUT_MS, logSyncDebug } from './orchestratorShared';
2
+ export function syncBootstrapPhaseTracking({ workspaceSyncPhase, lastBootstrapPhaseRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef }) {
3
+ if (workspaceSyncPhase === 'attach-cloud' || workspaceSyncPhase === 'provision-local') {
4
+ lastBootstrapPhaseRef.current = workspaceSyncPhase;
5
+ if (bootstrapPhaseStartedAtRef.current === null) {
6
+ bootstrapPhaseStartedAtRef.current = Date.now();
7
+ }
8
+ bootstrapLastProgressAtRef.current = null;
9
+ return;
10
+ }
11
+ bootstrapPhaseStartedAtRef.current = null;
12
+ bootstrapLastProgressAtRef.current = null;
13
+ }
14
+ export function createBootstrapTimeoutMonitor({ currentWorkspaceId, workspaceCloudSyncEnabled, workspaceSyncPhase, workspacePullInFlightRef, workspacePushInFlightRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceSyncBusy, persistWorkspaceSyncPatchSafely }) {
15
+ if (!workspaceCloudSyncEnabled)
16
+ return undefined;
17
+ if (workspaceSyncPhase !== 'attach-cloud' && workspaceSyncPhase !== 'provision-local')
18
+ return undefined;
19
+ const intervalId = window.setInterval(() => {
20
+ if (workspaceSyncPhase !== 'attach-cloud' && workspaceSyncPhase !== 'provision-local')
21
+ return;
22
+ if (workspacePullInFlightRef.current || workspacePushInFlightRef.current)
23
+ return;
24
+ const lastActivity = Math.max(bootstrapPhaseStartedAtRef.current ?? 0, bootstrapLastProgressAtRef.current ?? 0);
25
+ if (lastActivity > 0 && (Date.now() - lastActivity) < WORKSPACE_BOOTSTRAP_TIMEOUT_MS)
26
+ return;
27
+ const phaseLabel = workspaceSyncPhase === 'attach-cloud' ? 'initial cloud pull' : 'initial cloud upload';
28
+ const message = `Workspace sync ${phaseLabel} stalled. Press Repair to restart sync for this workspace.`;
29
+ setWorkspaceLastErrorMessage(message);
30
+ setWorkspaceLastErrorAt(new Date().toISOString());
31
+ setUserGlobalSyncStatus('error');
32
+ setUserGlobalSyncError(message);
33
+ setWorkspaceSyncBusy(false);
34
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
35
+ logSyncDebug('workspace_sync_bootstrap_timeout', {
36
+ workspaceId: currentWorkspaceId,
37
+ phase: workspaceSyncPhase
38
+ });
39
+ }, 10_000);
40
+ return () => window.clearInterval(intervalId);
41
+ }
42
+ export function resolveRepairPhase(args) {
43
+ const { workspaceSyncPhase, lastBootstrapPhase, workspaceLastPullAt } = args;
44
+ if (workspaceSyncPhase === 'provision-local')
45
+ return 'provision-local';
46
+ if (workspaceSyncPhase === 'attach-cloud')
47
+ return 'attach-cloud';
48
+ if (lastBootstrapPhase === 'provision-local' || lastBootstrapPhase === 'attach-cloud') {
49
+ return lastBootstrapPhase;
50
+ }
51
+ return workspaceLastPullAt ? 'attach-cloud' : 'provision-local';
52
+ }
@@ -0,0 +1,36 @@
1
+ import type { WorkspaceTaxonomyState } from '../../shared/taxonomyState.js';
2
+ import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncTaskEventSnapshot } from '../../sync/workspaceSyncModel';
3
+ import type { TaskItem } from '../../types';
4
+ export declare const USER_GLOBAL_SYNC_META_KEY = "taskforce.userGlobalSyncMeta.v1";
5
+ export declare const SYNC_DEBUG_LOG_KEY = "taskforce.syncDebug.v1";
6
+ export declare const WORKSPACE_BOOTSTRAP_TIMEOUT_MS = 120000;
7
+ export declare const WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX = "taskforce.syncWatermarks.v3";
8
+ export declare const WORKSPACE_FULL_RECONCILE_INTERVAL_MS: number;
9
+ export declare const WORKSPACE_CONCURRENT_WINDOW_MESSAGE = "Another local window is already syncing this workspace. Wait a few seconds, or press Repair in that window.";
10
+ export declare function isValidWorkspaceKey(workspaceId: string): boolean;
11
+ export declare function logSyncDebug(event: string, details?: Record<string, unknown>): void;
12
+ export declare function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry: any): WorkspaceSyncAiProfileSnapshot;
13
+ export declare function readUserGlobalSyncMeta(): Record<string, {
14
+ updatedAt: string;
15
+ }>;
16
+ export declare function writeUserGlobalSyncMeta(next: Record<string, {
17
+ updatedAt: string;
18
+ }>): void;
19
+ export declare function buildTaxonomyStateFingerprint(taxonomyState?: WorkspaceTaxonomyState): string;
20
+ export declare function flattenDocumentReviewComments(sessions: WorkspaceSyncDocumentReviewSessionSnapshot[]): WorkspaceSyncDocumentReviewCommentSnapshot[];
21
+ export declare function flattenTaskEvents(tasks: TaskItem[], archivedTasks: TaskItem[]): WorkspaceSyncTaskEventSnapshot[];
22
+ export interface PersistedSyncWatermarks {
23
+ taskWatermarks: Map<string, string>;
24
+ initiativeWatermarks: Map<string, string>;
25
+ workstreamWatermarks: Map<string, string>;
26
+ documentWatermarks: Map<string, string>;
27
+ assetWatermarks: Map<string, string>;
28
+ documentReviewSessionWatermarks: Map<string, string>;
29
+ documentReviewCommentWatermarks: Map<string, string>;
30
+ annotatedAttachmentSessionWatermarks: Map<string, string>;
31
+ taskEventWatermarks: Map<string, string>;
32
+ }
33
+ export declare function readPersistedSyncWatermarks(workspaceId: string, taxonomyStateFingerprint?: string): PersistedSyncWatermarks | null;
34
+ export declare function savePersistedSyncWatermarks(workspaceId: string, data: PersistedSyncWatermarks & {
35
+ taxonomyStateFingerprint?: string;
36
+ }): void;