@taskforcehq/taskforce 0.3.303 → 0.3.305
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { applyCollaborationSyncChanges, applyContentSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from './syncApplyHandlers.js';
|
|
3
|
+
function createCoreFixture(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
resolveTaskIdentifier: vi.fn().mockReturnValue(null),
|
|
6
|
+
getTask: vi.fn().mockReturnValue(null),
|
|
7
|
+
deleteTaskForSync: vi.fn(),
|
|
8
|
+
applyWorkspaceSyncSnapshot: vi.fn().mockReturnValue({
|
|
9
|
+
total: 1,
|
|
10
|
+
created: 0,
|
|
11
|
+
updated: 1,
|
|
12
|
+
archived: 0,
|
|
13
|
+
unarchived: 0,
|
|
14
|
+
skipped: 0,
|
|
15
|
+
}),
|
|
16
|
+
notifyDocumentMetadataMutation: vi.fn(),
|
|
17
|
+
getDocumentWatermark: vi.fn().mockReturnValue(null),
|
|
18
|
+
getTenantId: vi.fn().mockReturnValue('tenant-1'),
|
|
19
|
+
getDocumentReviewSession: vi.fn().mockReturnValue(null),
|
|
20
|
+
upsertDocumentReviewSessionForSync: vi.fn(),
|
|
21
|
+
listDocumentReviewCommentsForSync: vi.fn().mockReturnValue([]),
|
|
22
|
+
upsertDocumentReviewCommentForSync: vi.fn(),
|
|
23
|
+
deleteDocumentReviewSessionForSync: vi.fn(),
|
|
24
|
+
deleteDocumentReviewCommentForSync: vi.fn(),
|
|
25
|
+
getAnnotatedAttachmentSession: vi.fn().mockReturnValue(null),
|
|
26
|
+
upsertAnnotatedAttachmentSessionForSync: vi.fn(),
|
|
27
|
+
deleteAnnotatedAttachmentSessionForSync: vi.fn(),
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe('syncApplyHandlers', () => {
|
|
32
|
+
it('classifies task and workstream changes, then prepares and applies the task snapshot pipeline', () => {
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
const workspaceId = 'workspace-1';
|
|
35
|
+
const applyModel = classifyWorkspaceSyncChanges({
|
|
36
|
+
workspaceId,
|
|
37
|
+
incomingChanges: [
|
|
38
|
+
{
|
|
39
|
+
op: 'workstream-upsert',
|
|
40
|
+
workstream: {
|
|
41
|
+
id: 'workstream-1',
|
|
42
|
+
title: 'Delivery',
|
|
43
|
+
description: null,
|
|
44
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
45
|
+
updatedAt: '2026-04-10T09:00:00.000Z',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
op: 'upsert',
|
|
50
|
+
archived: false,
|
|
51
|
+
task: {
|
|
52
|
+
id: 'task-1',
|
|
53
|
+
title: 'Superseded task',
|
|
54
|
+
status: 'task',
|
|
55
|
+
category: 'general',
|
|
56
|
+
type: 'feature',
|
|
57
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
58
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
59
|
+
referenceNumber: 7,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
op: 'upsert',
|
|
64
|
+
archived: true,
|
|
65
|
+
task: {
|
|
66
|
+
id: 'task-2',
|
|
67
|
+
title: 'Archived task',
|
|
68
|
+
status: 'done',
|
|
69
|
+
category: 'general',
|
|
70
|
+
type: 'feature',
|
|
71
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
72
|
+
updatedAt: '2026-04-10T10:30:00.000Z',
|
|
73
|
+
isArchived: true,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
op: 'delete',
|
|
78
|
+
taskId: 'task-1',
|
|
79
|
+
deletedAt: '2026-04-10T11:00:00.000Z',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
op: 'delete',
|
|
83
|
+
taskId: 'task-stale',
|
|
84
|
+
deletedAt: '2026-04-10T08:00:00.000Z',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
88
|
+
normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
89
|
+
normalizeIncomingAiProfileSurfaceType: () => null,
|
|
90
|
+
normalizeTaskEventSyncPayload: () => null,
|
|
91
|
+
recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
|
|
92
|
+
diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(applyModel.planning.incomingWorkstreams.get('workstream-1')).toEqual(expect.objectContaining({
|
|
96
|
+
id: 'workstream-1',
|
|
97
|
+
title: 'Delivery',
|
|
98
|
+
}));
|
|
99
|
+
expect(applyModel.taskMutations.upserts).toHaveLength(2);
|
|
100
|
+
expect(applyModel.taskMutations.deletes.get('task-1')).toBe('2026-04-10T11:00:00.000Z');
|
|
101
|
+
const core = createCoreFixture({
|
|
102
|
+
resolveTaskIdentifier: vi.fn().mockReturnValue({
|
|
103
|
+
id: 'task-local-7',
|
|
104
|
+
title: 'Local incumbent',
|
|
105
|
+
referenceNumber: 7,
|
|
106
|
+
}),
|
|
107
|
+
getTask: vi.fn().mockImplementation((taskId) => {
|
|
108
|
+
if (taskId === 'task-1') {
|
|
109
|
+
return {
|
|
110
|
+
id: 'task-1',
|
|
111
|
+
title: 'Superseded task',
|
|
112
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
113
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
114
|
+
isArchived: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (taskId === 'task-stale') {
|
|
118
|
+
return {
|
|
119
|
+
id: 'task-stale',
|
|
120
|
+
title: 'Keep local task',
|
|
121
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
122
|
+
updatedAt: '2026-04-10T09:30:00.000Z',
|
|
123
|
+
isArchived: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
const prepared = prepareTaskSyncSnapshotApply({
|
|
130
|
+
core,
|
|
131
|
+
workspaceId,
|
|
132
|
+
taskMutations: applyModel.taskMutations,
|
|
133
|
+
recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
|
|
134
|
+
diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
expect(prepared.tasks).toEqual([]);
|
|
138
|
+
expect(prepared.archived).toEqual([
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
id: 'task-2',
|
|
141
|
+
title: 'Archived task',
|
|
142
|
+
}),
|
|
143
|
+
]);
|
|
144
|
+
expect(diagnostics).toEqual(expect.arrayContaining([
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
workspaceId,
|
|
147
|
+
eventType: 'apply',
|
|
148
|
+
message: 'Detected task reference number mismatch during normal sync.',
|
|
149
|
+
details: expect.objectContaining({
|
|
150
|
+
referenceLabel: 'T-7',
|
|
151
|
+
existingTaskId: 'task-local-7',
|
|
152
|
+
taskId: 'task-1',
|
|
153
|
+
}),
|
|
154
|
+
}),
|
|
155
|
+
]));
|
|
156
|
+
const deleteCount = applyTaskSyncDeletes({
|
|
157
|
+
core,
|
|
158
|
+
workspaceId,
|
|
159
|
+
taskMutations: applyModel.taskMutations,
|
|
160
|
+
recordSyncDiagnostic: vi.fn(),
|
|
161
|
+
});
|
|
162
|
+
expect(deleteCount).toBe(2);
|
|
163
|
+
expect(core.deleteTaskForSync).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(core.deleteTaskForSync).toHaveBeenCalledWith('task-1', workspaceId, { persistBackup: false });
|
|
165
|
+
const result = applyPreparedTaskSyncSnapshot({
|
|
166
|
+
core,
|
|
167
|
+
workspaceId,
|
|
168
|
+
prepared,
|
|
169
|
+
});
|
|
170
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
171
|
+
tasks: [],
|
|
172
|
+
archived: [
|
|
173
|
+
expect.objectContaining({
|
|
174
|
+
id: 'task-2',
|
|
175
|
+
title: 'Archived task',
|
|
176
|
+
isArchived: true,
|
|
177
|
+
}),
|
|
178
|
+
],
|
|
179
|
+
}, workspaceId);
|
|
180
|
+
expect(result).toEqual(expect.objectContaining({ total: 1 }));
|
|
181
|
+
});
|
|
182
|
+
it('applies document and asset changes through the content handler helpers', async () => {
|
|
183
|
+
const core = createCoreFixture();
|
|
184
|
+
const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
|
|
185
|
+
const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
|
|
186
|
+
const deleteWorkspaceSyncDocument = vi.fn();
|
|
187
|
+
const deleteWorkspaceSyncBinaryAsset = vi.fn();
|
|
188
|
+
await applyContentSyncChanges({
|
|
189
|
+
core,
|
|
190
|
+
workspaceId: 'workspace-1',
|
|
191
|
+
incomingDocuments: [
|
|
192
|
+
{
|
|
193
|
+
path: 'docs/spec.md',
|
|
194
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
195
|
+
content: '# Spec',
|
|
196
|
+
assetId: 'asset-doc-1',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
incomingDocumentDeletes: new Map([
|
|
200
|
+
['docs/stale.md', '2026-04-10T11:00:00.000Z'],
|
|
201
|
+
]),
|
|
202
|
+
incomingAssets: [
|
|
203
|
+
{
|
|
204
|
+
path: 'assets/proof.png',
|
|
205
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
206
|
+
contentBase64: Buffer.from('png-bytes').toString('base64'),
|
|
207
|
+
assetId: 'asset-image-1',
|
|
208
|
+
kind: 'image',
|
|
209
|
+
mimeType: 'image/png',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
incomingAssetDeletes: new Map([
|
|
213
|
+
['assets/stale.png', '2026-04-10T11:05:00.000Z'],
|
|
214
|
+
]),
|
|
215
|
+
basePath: '/tmp/taskforce-sync-content',
|
|
216
|
+
hasObjectStorageClient: () => false,
|
|
217
|
+
hasDocumentSyncMetadataDrift: () => false,
|
|
218
|
+
hasBinaryAssetSyncMetadataDrift: () => false,
|
|
219
|
+
getExistingDocument: () => null,
|
|
220
|
+
writeWorkspaceSyncDocument,
|
|
221
|
+
deleteWorkspaceSyncDocument,
|
|
222
|
+
writeWorkspaceSyncBinaryAsset,
|
|
223
|
+
deleteWorkspaceSyncBinaryAsset,
|
|
224
|
+
getExistingBinaryAsset: () => null,
|
|
225
|
+
recordSyncDiagnostic: vi.fn(),
|
|
226
|
+
});
|
|
227
|
+
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md', assetId: 'asset-doc-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
228
|
+
expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png', assetId: 'asset-image-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
229
|
+
expect(deleteWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'docs/stale.md', 'workspace-1');
|
|
230
|
+
expect(deleteWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'assets/stale.png', 'workspace-1');
|
|
231
|
+
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
|
|
232
|
+
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
|
|
233
|
+
});
|
|
234
|
+
it('only allows reference reassignment for content writes when the content changed or repair is explicit', async () => {
|
|
235
|
+
const core = createCoreFixture({
|
|
236
|
+
getDocumentWatermark: vi.fn().mockReturnValue(null),
|
|
237
|
+
});
|
|
238
|
+
const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
|
|
239
|
+
const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
|
|
240
|
+
await applyContentSyncChanges({
|
|
241
|
+
core,
|
|
242
|
+
workspaceId: 'workspace-1',
|
|
243
|
+
incomingDocuments: [
|
|
244
|
+
{
|
|
245
|
+
path: 'docs/spec.md',
|
|
246
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
247
|
+
content: '# Spec',
|
|
248
|
+
assetId: 'asset-doc-1',
|
|
249
|
+
referenceNumber: 22,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
incomingDocumentDeletes: new Map(),
|
|
253
|
+
incomingAssets: [
|
|
254
|
+
{
|
|
255
|
+
path: 'assets/proof.png',
|
|
256
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
257
|
+
contentBase64: Buffer.from('new-bytes').toString('base64'),
|
|
258
|
+
assetId: 'asset-image-1',
|
|
259
|
+
kind: 'image',
|
|
260
|
+
mimeType: 'image/png',
|
|
261
|
+
referenceNumber: 33,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
incomingAssetDeletes: new Map(),
|
|
265
|
+
basePath: '/tmp/taskforce-sync-content',
|
|
266
|
+
hasObjectStorageClient: () => false,
|
|
267
|
+
hasDocumentSyncMetadataDrift: () => true,
|
|
268
|
+
hasBinaryAssetSyncMetadataDrift: () => true,
|
|
269
|
+
getExistingDocument: () => ({
|
|
270
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
271
|
+
contentHash: '6bb50a17e2dc2fc14caea95a15cd8572018b6d22d74fb7b9ad015b5a0a8a935e',
|
|
272
|
+
}),
|
|
273
|
+
writeWorkspaceSyncDocument,
|
|
274
|
+
deleteWorkspaceSyncDocument: vi.fn(),
|
|
275
|
+
writeWorkspaceSyncBinaryAsset,
|
|
276
|
+
deleteWorkspaceSyncBinaryAsset: vi.fn(),
|
|
277
|
+
getExistingBinaryAsset: () => ({
|
|
278
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
279
|
+
contentSha256: 'old-hash',
|
|
280
|
+
}),
|
|
281
|
+
recordSyncDiagnostic: vi.fn(),
|
|
282
|
+
});
|
|
283
|
+
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: false });
|
|
284
|
+
expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
285
|
+
});
|
|
286
|
+
it('applies collaboration upserts and deletes through the collaboration handlers', () => {
|
|
287
|
+
const core = createCoreFixture();
|
|
288
|
+
applyCollaborationSyncChanges({
|
|
289
|
+
core,
|
|
290
|
+
workspaceId: 'workspace-1',
|
|
291
|
+
incomingDocumentReviewSessions: new Map([
|
|
292
|
+
['session-1', {
|
|
293
|
+
id: 'session-1',
|
|
294
|
+
assetId: 'asset-doc-1',
|
|
295
|
+
documentId: 'doc-1',
|
|
296
|
+
documentVersion: 3,
|
|
297
|
+
title: 'Review round',
|
|
298
|
+
status: 'open',
|
|
299
|
+
comments: [],
|
|
300
|
+
createdByActorId: 'user-1',
|
|
301
|
+
updatedByActorId: 'user-1',
|
|
302
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
303
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
304
|
+
deletedAt: null,
|
|
305
|
+
}],
|
|
306
|
+
]),
|
|
307
|
+
incomingDocumentReviewSessionDeletes: new Map([
|
|
308
|
+
['session-2', '2026-04-10T11:00:00.000Z'],
|
|
309
|
+
]),
|
|
310
|
+
incomingDocumentReviewComments: new Map([
|
|
311
|
+
['comment-1', {
|
|
312
|
+
id: 'comment-1',
|
|
313
|
+
sessionId: 'session-1',
|
|
314
|
+
body: 'Looks good',
|
|
315
|
+
anchor: { kind: 'document' },
|
|
316
|
+
order: 0,
|
|
317
|
+
authorActorId: 'user-1',
|
|
318
|
+
createdAt: '2026-04-10T09:05:00.000Z',
|
|
319
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
320
|
+
deletedAt: null,
|
|
321
|
+
}],
|
|
322
|
+
]),
|
|
323
|
+
incomingDocumentReviewCommentDeletes: new Map([
|
|
324
|
+
['comment-2', '2026-04-10T11:05:00.000Z'],
|
|
325
|
+
]),
|
|
326
|
+
incomingAnnotatedAttachmentSessions: new Map([
|
|
327
|
+
['annotation-session-1', {
|
|
328
|
+
id: 'annotation-session-1',
|
|
329
|
+
workspaceId: 'workspace-1',
|
|
330
|
+
taskId: 'task-1',
|
|
331
|
+
baseImageAssetId: 'asset-image-1',
|
|
332
|
+
title: 'Annotations',
|
|
333
|
+
globalInstruction: 'Inspect issue',
|
|
334
|
+
annotations: [],
|
|
335
|
+
createdByActorId: 'user-1',
|
|
336
|
+
updatedByActorId: 'user-1',
|
|
337
|
+
createdAt: '2026-04-10T09:10:00.000Z',
|
|
338
|
+
updatedAt: '2026-04-10T10:10:00.000Z',
|
|
339
|
+
deletedAt: null,
|
|
340
|
+
}],
|
|
341
|
+
]),
|
|
342
|
+
incomingAnnotatedAttachmentSessionDeletes: new Map([
|
|
343
|
+
['annotation-session-2', '2026-04-10T11:10:00.000Z'],
|
|
344
|
+
]),
|
|
345
|
+
recordSyncDiagnostic: vi.fn(),
|
|
346
|
+
createSyncRouteError: (message, details) => Object.assign(new Error(message), { details }),
|
|
347
|
+
});
|
|
348
|
+
expect(core.upsertDocumentReviewSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
349
|
+
id: 'session-1',
|
|
350
|
+
workspaceId: 'workspace-1',
|
|
351
|
+
assetId: 'asset-doc-1',
|
|
352
|
+
}));
|
|
353
|
+
expect(core.deleteDocumentReviewSessionForSync).toHaveBeenCalledWith({
|
|
354
|
+
workspaceId: 'workspace-1',
|
|
355
|
+
sessionId: 'session-2',
|
|
356
|
+
deletedAt: '2026-04-10T11:00:00.000Z',
|
|
357
|
+
});
|
|
358
|
+
expect(core.upsertDocumentReviewCommentForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
359
|
+
id: 'comment-1',
|
|
360
|
+
sessionId: 'session-1',
|
|
361
|
+
workspaceId: 'workspace-1',
|
|
362
|
+
}));
|
|
363
|
+
expect(core.deleteDocumentReviewCommentForSync).toHaveBeenCalledWith({
|
|
364
|
+
workspaceId: 'workspace-1',
|
|
365
|
+
commentId: 'comment-2',
|
|
366
|
+
deletedAt: '2026-04-10T11:05:00.000Z',
|
|
367
|
+
});
|
|
368
|
+
expect(core.upsertAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
369
|
+
id: 'annotation-session-1',
|
|
370
|
+
workspaceId: 'workspace-1',
|
|
371
|
+
baseImageAssetId: 'asset-image-1',
|
|
372
|
+
}));
|
|
373
|
+
expect(core.deleteAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith({
|
|
374
|
+
workspaceId: 'workspace-1',
|
|
375
|
+
sessionId: 'annotation-session-2',
|
|
376
|
+
deletedAt: '2026-04-10T11:10:00.000Z',
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkspaceSyncPayload } from './workspaceSyncModel';
|
|
1
|
+
import type { WorkspaceSyncPayload, WorkspaceSyncTaskSnapshot } from './workspaceSyncModel';
|
|
2
2
|
export declare function isTransientWorkspaceSyncStatus(status: number | null | undefined): boolean;
|
|
3
3
|
export interface EnsureWorkspaceReadyInput {
|
|
4
4
|
cloudAuthConfigured: boolean;
|
|
@@ -7,6 +7,7 @@ export interface EnsureWorkspaceReadyInput {
|
|
|
7
7
|
resolveCloudAuthUrl: (path: string) => string;
|
|
8
8
|
workspaceId: string;
|
|
9
9
|
workspaceName: string;
|
|
10
|
+
allowProvisionOnMismatch?: boolean;
|
|
10
11
|
}
|
|
11
12
|
export interface EnsureWorkspaceReadyResult {
|
|
12
13
|
success: boolean;
|
|
@@ -77,10 +78,16 @@ export interface WorkspacePushSyncResult {
|
|
|
77
78
|
currentAnnotatedAttachmentSessionIds: Set<string>;
|
|
78
79
|
deleteAnnotatedAttachmentSessionIds: Set<string>;
|
|
79
80
|
currentAnnotatedAttachmentSessionWatermarks: Map<string, string>;
|
|
81
|
+
currentTaskEventWatermarks: Map<string, string>;
|
|
80
82
|
/** Watermarks for every task upsert that was successfully pushed. */
|
|
81
83
|
pushedWatermarks: Map<string, string>;
|
|
82
84
|
/** Server-emitted realtime mutation event ids for this push apply. */
|
|
83
85
|
emittedEventIds: string[];
|
|
86
|
+
/** Authoritative task snapshots returned immediately by the push route for incoming task upserts. */
|
|
87
|
+
appliedTaskUpserts: Array<{
|
|
88
|
+
archived: boolean;
|
|
89
|
+
task: WorkspaceSyncTaskSnapshot;
|
|
90
|
+
}>;
|
|
84
91
|
}
|
|
85
92
|
export declare function runWorkspacePushSyncService(input: WorkspacePushSyncInput): Promise<WorkspacePushSyncResult>;
|
|
86
93
|
export type WorkspacePullFailureKind = 'pull_http' | 'apply_auth' | 'apply_all_candidates' | 'apply_payload' | 'exception';
|
|
@@ -113,6 +120,7 @@ export interface WorkspacePullSyncResult {
|
|
|
113
120
|
pulledDeleteTaskIds: Set<string>;
|
|
114
121
|
pulledActiveUpserts: boolean;
|
|
115
122
|
pulledArchivedUpserts: boolean;
|
|
123
|
+
pulledTaskEventUpserts?: boolean;
|
|
116
124
|
documentDecryptFailures?: string[];
|
|
117
125
|
emittedEventIds?: string[];
|
|
118
126
|
serverDiagnostics?: Record<string, unknown> | null;
|
package/dist/sync/syncService.js
CHANGED
|
@@ -45,6 +45,14 @@ export async function ensureCloudWorkspaceReadyForSyncService(input) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
if (handshakeResult.status === 409 && data.code === 'WORKSPACE_ID_MISMATCH') {
|
|
48
|
+
if (input.allowProvisionOnMismatch === false) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
statusCode: 409,
|
|
52
|
+
transient: false,
|
|
53
|
+
error: String(data.error || 'Selected cloud workspace could not be attached to this local workspace.')
|
|
54
|
+
};
|
|
55
|
+
}
|
|
48
56
|
const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
|
|
49
57
|
workspaceId: input.workspaceId,
|
|
50
58
|
name: input.workspaceName || input.workspaceId
|
|
@@ -187,8 +195,10 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
187
195
|
currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
|
|
188
196
|
deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
|
|
189
197
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
198
|
+
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
190
199
|
pushedWatermarks: new Map(),
|
|
191
|
-
emittedEventIds: []
|
|
200
|
+
emittedEventIds: [],
|
|
201
|
+
appliedTaskUpserts: []
|
|
192
202
|
};
|
|
193
203
|
}
|
|
194
204
|
const pushData = asObjectRecord(pushResult.data);
|
|
@@ -197,6 +207,23 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
197
207
|
.map((entry) => String(entry || '').trim())
|
|
198
208
|
.filter(Boolean)))
|
|
199
209
|
: [];
|
|
210
|
+
const appliedTaskUpserts = Array.isArray(pushData.appliedTaskUpserts)
|
|
211
|
+
? pushData.appliedTaskUpserts
|
|
212
|
+
.map((entry) => {
|
|
213
|
+
const source = entry && typeof entry === 'object' ? entry : {};
|
|
214
|
+
const task = source.task && typeof source.task === 'object'
|
|
215
|
+
? source.task
|
|
216
|
+
: null;
|
|
217
|
+
const taskId = String(task?.id || '').trim();
|
|
218
|
+
if (!task || !taskId)
|
|
219
|
+
return null;
|
|
220
|
+
return {
|
|
221
|
+
archived: source.archived === true,
|
|
222
|
+
task
|
|
223
|
+
};
|
|
224
|
+
})
|
|
225
|
+
.filter((entry) => Boolean(entry))
|
|
226
|
+
: [];
|
|
200
227
|
return {
|
|
201
228
|
success: true,
|
|
202
229
|
syncedAt: new Date().toISOString(),
|
|
@@ -226,8 +253,10 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
226
253
|
currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
|
|
227
254
|
deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
|
|
228
255
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
256
|
+
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
229
257
|
pushedWatermarks: input.payload.pushedWatermarks,
|
|
230
|
-
emittedEventIds
|
|
258
|
+
emittedEventIds,
|
|
259
|
+
appliedTaskUpserts
|
|
231
260
|
};
|
|
232
261
|
}
|
|
233
262
|
export async function runWorkspacePullSyncService(input) {
|
|
@@ -240,6 +269,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
240
269
|
const pulledDeleteTaskIds = new Set();
|
|
241
270
|
let pulledActiveUpserts = false;
|
|
242
271
|
let pulledArchivedUpserts = false;
|
|
272
|
+
let pulledTaskEventUpserts = false;
|
|
243
273
|
const documentDecryptFailures = new Set();
|
|
244
274
|
const emittedEventIds = new Set();
|
|
245
275
|
const pulledAnnotatedAttachmentSessionIds = new Set();
|
|
@@ -280,6 +310,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
280
310
|
pulledDeleteTaskIds,
|
|
281
311
|
pulledActiveUpserts,
|
|
282
312
|
pulledArchivedUpserts,
|
|
313
|
+
pulledTaskEventUpserts,
|
|
283
314
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
284
315
|
serverDiagnostics: null
|
|
285
316
|
};
|
|
@@ -309,6 +340,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
309
340
|
pulledAnnotatedAttachmentSessionIds.add(sessionId);
|
|
310
341
|
continue;
|
|
311
342
|
}
|
|
343
|
+
if (change.op === 'task-event-upsert') {
|
|
344
|
+
pulledTaskEventUpserts = true;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
312
347
|
if (change.op === 'document-upsert') {
|
|
313
348
|
if (typeof change.content === 'string' && typeof process !== 'undefined' && process.env?.TASKFORCE_SYNC_KEY) {
|
|
314
349
|
try {
|
|
@@ -371,6 +406,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
371
406
|
pulledDeleteTaskIds,
|
|
372
407
|
pulledActiveUpserts,
|
|
373
408
|
pulledArchivedUpserts,
|
|
409
|
+
pulledTaskEventUpserts,
|
|
374
410
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
375
411
|
serverDiagnostics
|
|
376
412
|
};
|
|
@@ -398,6 +434,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
398
434
|
pulledDeleteTaskIds,
|
|
399
435
|
pulledActiveUpserts,
|
|
400
436
|
pulledArchivedUpserts,
|
|
437
|
+
pulledTaskEventUpserts,
|
|
401
438
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
402
439
|
emittedEventIds: Array.from(emittedEventIds),
|
|
403
440
|
serverDiagnostics: lastServerDiagnostics
|
|
@@ -419,6 +456,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
419
456
|
pulledDeleteTaskIds,
|
|
420
457
|
pulledActiveUpserts,
|
|
421
458
|
pulledArchivedUpserts,
|
|
459
|
+
pulledTaskEventUpserts,
|
|
422
460
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
423
461
|
emittedEventIds: Array.from(emittedEventIds),
|
|
424
462
|
serverDiagnostics: lastServerDiagnostics
|
|
@@ -469,6 +507,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
469
507
|
pulledDeleteTaskIds,
|
|
470
508
|
pulledActiveUpserts,
|
|
471
509
|
pulledArchivedUpserts,
|
|
510
|
+
pulledTaskEventUpserts,
|
|
472
511
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
473
512
|
emittedEventIds: Array.from(emittedEventIds),
|
|
474
513
|
serverDiagnostics: lastServerDiagnostics
|
|
@@ -497,6 +536,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
497
536
|
pulledDeleteTaskIds,
|
|
498
537
|
pulledActiveUpserts,
|
|
499
538
|
pulledArchivedUpserts,
|
|
539
|
+
pulledTaskEventUpserts,
|
|
500
540
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
501
541
|
emittedEventIds: Array.from(emittedEventIds),
|
|
502
542
|
serverDiagnostics: lastServerDiagnostics
|
|
@@ -515,6 +555,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
515
555
|
pulledDeleteTaskIds,
|
|
516
556
|
pulledActiveUpserts,
|
|
517
557
|
pulledArchivedUpserts,
|
|
558
|
+
pulledTaskEventUpserts,
|
|
518
559
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
519
560
|
emittedEventIds: Array.from(emittedEventIds),
|
|
520
561
|
serverDiagnostics: lastServerDiagnostics
|
|
@@ -535,6 +576,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
535
576
|
pulledDeleteTaskIds,
|
|
536
577
|
pulledActiveUpserts,
|
|
537
578
|
pulledArchivedUpserts,
|
|
579
|
+
pulledTaskEventUpserts,
|
|
538
580
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
539
581
|
emittedEventIds: Array.from(emittedEventIds),
|
|
540
582
|
serverDiagnostics: null
|
|
@@ -37,6 +37,7 @@ function buildPayload(overrides = {}) {
|
|
|
37
37
|
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
38
38
|
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
39
39
|
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
40
|
+
currentTaskEventWatermarks: new Map(),
|
|
40
41
|
pushedWatermarks: new Map(),
|
|
41
42
|
...overrides
|
|
42
43
|
};
|
|
@@ -500,6 +501,53 @@ describe('syncService', () => {
|
|
|
500
501
|
delete process.env.TASKFORCE_SYNC_KEY;
|
|
501
502
|
}
|
|
502
503
|
});
|
|
504
|
+
it('tracks event-only task pulls so callers can refresh visible task history', async () => {
|
|
505
|
+
vi.mocked(pullWorkspaceChanges).mockResolvedValueOnce({
|
|
506
|
+
ok: true,
|
|
507
|
+
status: 200,
|
|
508
|
+
data: {
|
|
509
|
+
success: true,
|
|
510
|
+
changes: [{
|
|
511
|
+
op: 'task-event-upsert',
|
|
512
|
+
event: {
|
|
513
|
+
id: 'task-event-1',
|
|
514
|
+
taskId: 'task-1',
|
|
515
|
+
workspaceId: 'ws-1',
|
|
516
|
+
action: 'task-status-changed',
|
|
517
|
+
actor: 'ai-profile-1',
|
|
518
|
+
actorType: 'ai',
|
|
519
|
+
createdAt: '2026-04-13T10:05:00.000Z',
|
|
520
|
+
details: {
|
|
521
|
+
changes: {
|
|
522
|
+
status: {
|
|
523
|
+
from: 'in-progress',
|
|
524
|
+
to: 'done'
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}],
|
|
530
|
+
nextCursor: null,
|
|
531
|
+
hasMore: false
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
vi.mocked(applyLocalWorkspaceChanges).mockResolvedValueOnce({
|
|
535
|
+
ok: true,
|
|
536
|
+
failures: [],
|
|
537
|
+
appliedCount: 1,
|
|
538
|
+
data: { success: true }
|
|
539
|
+
});
|
|
540
|
+
const res = await runWorkspacePullSyncService({
|
|
541
|
+
resolveCloudAuthUrl: mockResolveCloudAuthUrl,
|
|
542
|
+
workspaceId: 'ws-1',
|
|
543
|
+
cursor: null,
|
|
544
|
+
bootstrap: false
|
|
545
|
+
});
|
|
546
|
+
expect(res.success).toBe(true);
|
|
547
|
+
expect(res.pulledTaskEventUpserts).toBe(true);
|
|
548
|
+
expect(res.pulledActiveUpserts).toBe(false);
|
|
549
|
+
expect(res.pulledArchivedUpserts).toBe(false);
|
|
550
|
+
});
|
|
503
551
|
it('zero-change bootstrap pull (no changes, hasMore false) returns success true', async () => {
|
|
504
552
|
vi.mocked(pullWorkspaceChanges).mockResolvedValueOnce({
|
|
505
553
|
ok: true,
|
|
@@ -695,6 +743,20 @@ describe('syncService', () => {
|
|
|
695
743
|
expect(res.error).toContain('already linked to another cloud account');
|
|
696
744
|
expect(res.error).toContain('grant you access');
|
|
697
745
|
});
|
|
746
|
+
it('does not provision a new cloud workspace during attach-cloud mismatch handling', async () => {
|
|
747
|
+
vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
|
|
748
|
+
ok: false,
|
|
749
|
+
status: 409,
|
|
750
|
+
data: { code: 'WORKSPACE_ID_MISMATCH', error: 'Workspace mismatch' }
|
|
751
|
+
});
|
|
752
|
+
const res = await ensureCloudWorkspaceReadyForSyncService({
|
|
753
|
+
...baseHandshakeInput,
|
|
754
|
+
allowProvisionOnMismatch: false
|
|
755
|
+
});
|
|
756
|
+
expect(res.success).toBe(false);
|
|
757
|
+
expect(res.statusCode).toBe(409);
|
|
758
|
+
expect(provisionWorkspace).not.toHaveBeenCalled();
|
|
759
|
+
});
|
|
698
760
|
});
|
|
699
761
|
describe('syncUserGlobalSettingsService transient metadata', () => {
|
|
700
762
|
beforeEach(() => {
|