@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
@@ -1,11 +1,12 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- export function useResourceExport({ storagePath }) {
2
+ export function useResourceExport({ storagePath, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
3
3
  const [exportEnvironment, setExportEnvironment] = useState('antigravity');
4
4
  const [availableEnvironments, setAvailableEnvironments] = useState([]);
5
5
  const [exportWorkflowsPath, setExportWorkflowsPath] = useState('.agent/workflows');
6
6
  const [exportingResource, setExportingResource] = useState(null);
7
7
  const [exportResult, setExportResult] = useState(null);
8
8
  const exportResultTimerRef = useRef(null);
9
+ const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
9
10
  useEffect(() => {
10
11
  const env = availableEnvironments.find((item) => item.id === exportEnvironment);
11
12
  if (env) {
@@ -23,13 +24,15 @@ export function useResourceExport({ storagePath }) {
23
24
  }, 5000);
24
25
  }, []);
25
26
  const loadAvailableEnvironments = useCallback(async () => {
27
+ if (shouldSkipProtectedApiCalls)
28
+ return;
26
29
  const envRes = await fetch('/api/taskforce/environments');
27
30
  if (!envRes.ok)
28
31
  return;
29
32
  const envData = await envRes.json().catch(() => ({}));
30
33
  if (envData.environments)
31
34
  setAvailableEnvironments(envData.environments);
32
- }, []);
35
+ }, [shouldSkipProtectedApiCalls]);
33
36
  const handleExportWorkflows = useCallback(async (environment, selected) => {
34
37
  setExportingResource('workflows');
35
38
  setExportResult(null);
@@ -2,8 +2,9 @@ import { useCallback, useState } from 'react';
2
2
  export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
3
3
  const [availableWorkflows, setAvailableWorkflows] = useState([]);
4
4
  const [initiativeTemplates, setInitiativeTemplates] = useState([]);
5
+ const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
5
6
  const fetchWorkflows = useCallback(async () => {
6
- if (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls)
7
+ if (shouldSkipProtectedApiCalls)
7
8
  return;
8
9
  try {
9
10
  const res = await fetch('/api/taskforce/workflow-templates');
@@ -15,8 +16,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
15
16
  catch {
16
17
  // Ignore error
17
18
  }
18
- }, [shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
19
+ }, [shouldSkipProtectedApiCalls]);
19
20
  const fetchInitiativeTemplates = useCallback(async () => {
21
+ if (shouldSkipProtectedApiCalls)
22
+ return [];
20
23
  try {
21
24
  const res = await fetch('/api/taskforce/initiative-templates');
22
25
  if (!res.ok)
@@ -29,9 +32,9 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
29
32
  catch {
30
33
  return [];
31
34
  }
32
- }, []);
35
+ }, [shouldSkipProtectedApiCalls]);
33
36
  const fetchWorkflowTemplate = useCallback(async (name) => {
34
- if (!name)
37
+ if (!name || shouldSkipProtectedApiCalls)
35
38
  return null;
36
39
  const paths = [
37
40
  `/api/taskforce/workflow-template/${encodeURIComponent(name)}`,
@@ -51,8 +54,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
51
54
  catch {
52
55
  return null;
53
56
  }
54
- }, []);
57
+ }, [shouldSkipProtectedApiCalls]);
55
58
  const fetchWorkflowOverrideNames = useCallback(async () => {
59
+ if (shouldSkipProtectedApiCalls)
60
+ return [];
56
61
  try {
57
62
  const res = await fetch('/api/taskforce/workflow-editor/overrides');
58
63
  if (!res.ok)
@@ -63,7 +68,7 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
63
68
  catch {
64
69
  return [];
65
70
  }
66
- }, []);
71
+ }, [shouldSkipProtectedApiCalls]);
67
72
  const saveWorkflowTemplateDraft = useCallback(async (name, draft) => {
68
73
  try {
69
74
  const res = await fetch('/api/taskforce/workflow-editor/save', {
@@ -168,7 +168,8 @@ export function useRealtimeSync(input) {
168
168
  if (event.type === 'taskforce:update') {
169
169
  queueSignal({
170
170
  type: 'taskforce:update',
171
- workspaceId: normalizedWorkspaceId
171
+ workspaceId: normalizedWorkspaceId,
172
+ eventId: typeof event.eventId === 'string' ? event.eventId.trim() : undefined
172
173
  });
173
174
  return;
174
175
  }
@@ -10,6 +10,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
10
10
  username: 'codex',
11
11
  icon: 'Bot',
12
12
  color: '#0ea5e9',
13
+ description: 'Terminal-first coding agent',
14
+ role: 'Implementer',
15
+ provider: 'OpenAI',
16
+ model: 'gpt-5.4',
13
17
  surface_type: 'coding_tool',
14
18
  provider_metadata: { source: 'test' },
15
19
  created_at: '2026-03-15T18:00:00.000Z',
@@ -24,6 +28,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
24
28
  username: 'codex',
25
29
  icon: 'Bot',
26
30
  color: '#0ea5e9',
31
+ description: 'Terminal-first coding agent',
32
+ role: 'Implementer',
33
+ provider: 'OpenAI',
34
+ model: 'gpt-5.4',
27
35
  surfaceType: 'coding_tool',
28
36
  providerMetadata: { source: 'test' },
29
37
  createdAt: '2026-03-15T18:00:00.000Z',
@@ -0,0 +1,228 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { act, renderHook, waitFor } from '@testing-library/react';
3
+ import { useSyncOrchestrator } from './useSyncOrchestrator';
4
+ const { ensureCloudWorkspaceReadyForSyncServiceMock, runWorkspacePullSyncServiceMock, runWorkspacePushSyncServiceMock, syncUserGlobalSettingsServiceMock } = vi.hoisted(() => ({
5
+ ensureCloudWorkspaceReadyForSyncServiceMock: vi.fn(),
6
+ runWorkspacePullSyncServiceMock: vi.fn(),
7
+ runWorkspacePushSyncServiceMock: vi.fn(),
8
+ syncUserGlobalSettingsServiceMock: vi.fn()
9
+ }));
10
+ vi.mock('../sync/syncService', async () => {
11
+ const actual = await vi.importActual('../sync/syncService');
12
+ return {
13
+ ...actual,
14
+ calculateWorkspaceRetryDelayMs: vi.fn(() => 100),
15
+ ensureCloudWorkspaceReadyForSyncService: ensureCloudWorkspaceReadyForSyncServiceMock,
16
+ runWorkspacePullSyncService: runWorkspacePullSyncServiceMock,
17
+ runWorkspacePushSyncService: runWorkspacePushSyncServiceMock,
18
+ syncUserGlobalSettingsService: syncUserGlobalSettingsServiceMock
19
+ };
20
+ });
21
+ function buildInput(overrides) {
22
+ return {
23
+ currentWorkspaceId: 'workspace-local-active',
24
+ cloudAuthConfigured: true,
25
+ runtimeMode: 'local',
26
+ authSessionResolved: true,
27
+ isAuthenticated: true,
28
+ authUserId: 'user-1',
29
+ projectName: 'Test Project',
30
+ resolveCloudAuthUrl: (path) => `https://cloud.example.com${path}`,
31
+ resolveWebSocketUrl: (path) => `wss://api.example.com${path}`,
32
+ realtimeSyncEnabled: false,
33
+ tasks: [],
34
+ archivedTasks: [],
35
+ initiatives: [],
36
+ workstreams: [],
37
+ taxonomies: [],
38
+ setupState: null,
39
+ globalTheme: 'dark',
40
+ locale: 'en-US',
41
+ globalWeekStartsOn: 'monday',
42
+ themeUseGlobalDefault: true,
43
+ setTasks: vi.fn(),
44
+ setArchivedTasks: vi.fn(),
45
+ setAuthBlocked: vi.fn(),
46
+ setIsAuthenticated: vi.fn(),
47
+ checkAuthSession: vi.fn(async () => true),
48
+ setGlobalTheme: vi.fn(),
49
+ setCurrentTheme: vi.fn(),
50
+ setSetupState: vi.fn(),
51
+ setLocale: vi.fn(),
52
+ setGlobalWeekStartsOn: vi.fn(),
53
+ fetchPlanningEntities: vi.fn(async () => { }),
54
+ ...overrides
55
+ };
56
+ }
57
+ describe('useSyncOrchestrator context asset refreshes', () => {
58
+ beforeEach(() => {
59
+ ensureCloudWorkspaceReadyForSyncServiceMock.mockReset();
60
+ runWorkspacePullSyncServiceMock.mockReset();
61
+ runWorkspacePushSyncServiceMock.mockReset();
62
+ syncUserGlobalSettingsServiceMock.mockReset();
63
+ ensureCloudWorkspaceReadyForSyncServiceMock.mockResolvedValue({ success: true });
64
+ runWorkspacePullSyncServiceMock.mockResolvedValue({
65
+ success: true,
66
+ syncedAt: '2026-03-15T18:00:00.000Z',
67
+ cursor: null,
68
+ pages: 0,
69
+ appliedChanges: 0,
70
+ pullRequestMs: 1,
71
+ applyMs: 0,
72
+ pulledDeleteTaskIds: new Set(),
73
+ pulledActiveUpserts: false,
74
+ pulledArchivedUpserts: false,
75
+ documentDecryptFailures: []
76
+ });
77
+ runWorkspacePushSyncServiceMock.mockResolvedValue({
78
+ success: true,
79
+ syncedAt: '2026-03-15T18:00:00.000Z',
80
+ requestMs: 1,
81
+ changeCount: 0,
82
+ deleteCount: 0,
83
+ currentTaskIds: new Set(),
84
+ currentInitiativeIds: new Set(),
85
+ currentInitiativeWatermarks: new Map(),
86
+ currentWorkstreamIds: new Set(),
87
+ currentWorkstreamWatermarks: new Map(),
88
+ currentAiProfileIds: new Set(),
89
+ currentAiProfileWatermarks: new Map(),
90
+ currentDocumentPaths: new Set(),
91
+ currentDocumentWatermarks: new Map(),
92
+ currentAssetPaths: new Set(),
93
+ currentAssetWatermarks: new Map(),
94
+ currentDocumentReviewSessionIds: new Set(),
95
+ currentDocumentReviewSessionWatermarks: new Map(),
96
+ currentDocumentReviewCommentIds: new Set(),
97
+ currentDocumentReviewCommentWatermarks: new Map(),
98
+ currentAnnotatedAttachmentSessionIds: new Set(),
99
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
100
+ currentTaskEventWatermarks: new Map(),
101
+ deleteTaskIds: new Set(),
102
+ pushedWatermarks: new Map(),
103
+ emittedEventIds: []
104
+ });
105
+ syncUserGlobalSettingsServiceMock.mockResolvedValue({
106
+ success: true,
107
+ mode: 'pulled',
108
+ updatedAt: new Date().toISOString()
109
+ });
110
+ });
111
+ afterEach(() => {
112
+ vi.unstubAllGlobals();
113
+ vi.clearAllMocks();
114
+ });
115
+ it('refreshes only document and asset snapshots when task attachments change', async () => {
116
+ const fetchCounts = {
117
+ documents: 0,
118
+ assets: 0,
119
+ aiProfiles: 0,
120
+ documentReviews: 0,
121
+ annotatedAttachments: 0
122
+ };
123
+ vi.stubGlobal('fetch', vi.fn((url, init) => {
124
+ if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
125
+ return Promise.resolve({
126
+ ok: true,
127
+ json: () => Promise.resolve({
128
+ success: true,
129
+ state: {
130
+ version: 2,
131
+ enabled: true,
132
+ phase: 'active',
133
+ pullCursor: null,
134
+ lastPullAt: null,
135
+ lastPushAt: null,
136
+ lastSyncedAt: null
137
+ }
138
+ })
139
+ });
140
+ }
141
+ if (url.includes('/api/taskforce/ui-state') && String(init?.method || '').toUpperCase() === 'POST') {
142
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
143
+ }
144
+ if (url.includes('/api/taskforce/sync/workspace/documents')) {
145
+ fetchCounts.documents += 1;
146
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ documents: [] }) });
147
+ }
148
+ if (url.includes('/api/taskforce/sync/workspace/assets')) {
149
+ fetchCounts.assets += 1;
150
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ assets: [] }) });
151
+ }
152
+ if (url.includes('/api/taskforce/sync/workspace/ai-profiles')) {
153
+ fetchCounts.aiProfiles += 1;
154
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ aiProfiles: [] }) });
155
+ }
156
+ if (url.includes('/api/taskforce/sync/workspace/document-reviews')) {
157
+ fetchCounts.documentReviews += 1;
158
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ sessions: [], comments: [] }) });
159
+ }
160
+ if (url.includes('/api/taskforce/sync/workspace/annotated-attachments')) {
161
+ fetchCounts.annotatedAttachments += 1;
162
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ sessions: [] }) });
163
+ }
164
+ if (url.includes('/api/taskforce/sync/events')) {
165
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
166
+ }
167
+ if (url.includes('/api/taskforce/tasks')) {
168
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: [] }) });
169
+ }
170
+ if (url.includes('/api/taskforce/archive')) {
171
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ archived: [] }) });
172
+ }
173
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
174
+ }));
175
+ const baseTask = {
176
+ id: 'task-with-attachments',
177
+ title: 'Task with attachment refreshes',
178
+ description: '',
179
+ category: 'general',
180
+ type: 'task',
181
+ priority: 2,
182
+ complexity: 3,
183
+ status: 'task',
184
+ createdAt: '2026-03-15T12:00:00.000Z',
185
+ updatedAt: '2026-03-15T12:00:00.000Z',
186
+ attachments: [],
187
+ taskEvents: []
188
+ };
189
+ const initialInput = buildInput({
190
+ tasks: [baseTask]
191
+ });
192
+ const { result, rerender, unmount } = renderHook((props) => useSyncOrchestrator(props), {
193
+ initialProps: initialInput
194
+ });
195
+ act(() => {
196
+ result.current.applyWorkspaceSyncStateSnapshot({
197
+ enabled: true,
198
+ phase: 'active',
199
+ pullCursor: null
200
+ });
201
+ });
202
+ await waitFor(() => {
203
+ expect(fetchCounts.documents).toBeGreaterThan(0);
204
+ expect(fetchCounts.assets).toBeGreaterThan(0);
205
+ expect(fetchCounts.aiProfiles).toBeGreaterThan(0);
206
+ expect(fetchCounts.documentReviews).toBeGreaterThan(0);
207
+ });
208
+ const baselineCounts = { ...fetchCounts };
209
+ rerender({
210
+ ...initialInput,
211
+ tasks: [{
212
+ ...baseTask,
213
+ attachments: [{
214
+ id: 'asset-1',
215
+ path: 'workspaces/workspace-local-active/assets/file.txt'
216
+ }]
217
+ }]
218
+ });
219
+ await waitFor(() => {
220
+ expect(fetchCounts.documents).toBeGreaterThan(baselineCounts.documents);
221
+ expect(fetchCounts.assets).toBeGreaterThan(baselineCounts.assets);
222
+ });
223
+ expect(fetchCounts.aiProfiles).toBe(baselineCounts.aiProfiles);
224
+ expect(fetchCounts.documentReviews).toBe(baselineCounts.documentReviews);
225
+ expect(fetchCounts.annotatedAttachments).toBe(baselineCounts.annotatedAttachments);
226
+ unmount();
227
+ });
228
+ });
@@ -1,9 +1,8 @@
1
1
  import type { InitiativeItem, TaskItem, TaskforceTheme, WorkstreamItem } from '../types';
2
2
  import { type WorkspaceSyncPhase } from '../sync/workspaceSyncState';
3
- import { type WorkspaceSyncAiProfileSnapshot } from '../sync/workspaceSyncModel';
4
3
  import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
5
4
  import type { SupportedLocale } from '../localization';
6
- export declare function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry: any): WorkspaceSyncAiProfileSnapshot;
5
+ import { normalizeWorkspaceSyncAiProfileSnapshotEntry } from './sync/orchestratorShared';
7
6
  export interface UseSyncOrchestratorInput {
8
7
  currentWorkspaceId: string;
9
8
  cloudAuthConfigured: boolean;
@@ -41,9 +40,10 @@ export interface UseSyncOrchestratorInput {
41
40
  fetchPlanningEntities: () => Promise<void>;
42
41
  }
43
42
  export type WorkspaceSyncStatus = 'off' | 'syncing' | 'attention' | 'healthy';
43
+ export { normalizeWorkspaceSyncAiProfileSnapshotEntry };
44
44
  export declare function useSyncOrchestrator(input: UseSyncOrchestratorInput): {
45
- userGlobalSyncStatus: "error" | "idle" | "syncing" | "disconnected";
46
- setUserGlobalSyncStatus: import("react").Dispatch<import("react").SetStateAction<"error" | "idle" | "syncing" | "disconnected">>;
45
+ userGlobalSyncStatus: "error" | "idle" | "disconnected" | "syncing";
46
+ setUserGlobalSyncStatus: import("react").Dispatch<import("react").SetStateAction<"error" | "idle" | "disconnected" | "syncing">>;
47
47
  userGlobalSyncError: string | null;
48
48
  setUserGlobalSyncError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
49
49
  workspaceLastPullAt: string | null;
@@ -85,6 +85,7 @@ export declare function useSyncOrchestrator(input: UseSyncOrchestratorInput): {
85
85
  }) => Promise<boolean>;
86
86
  pullWorkspaceChangesFromCloud: (options?: {
87
87
  repairMode?: boolean;
88
+ forceCursorNull?: boolean;
88
89
  }) => Promise<boolean>;
89
90
  saveWorkspaceCloudSyncSettings: (settings: {
90
91
  enabled: boolean;