@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.
- package/dist/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- 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 +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- 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 +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- 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/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- 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 +61 -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 +122 -0
- package/dist/hooks/sync/transfers.js +431 -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 +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- 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/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/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- 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/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -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.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +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.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- 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 +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -0,0 +1,377 @@
|
|
|
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
|
+
},
|
|
80
|
+
{
|
|
81
|
+
op: 'delete',
|
|
82
|
+
taskId: 'task-stale',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
86
|
+
normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
87
|
+
normalizeIncomingAiProfileSurfaceType: () => null,
|
|
88
|
+
normalizeTaskEventSyncPayload: () => null,
|
|
89
|
+
recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
|
|
90
|
+
diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(applyModel.planning.incomingWorkstreams.get('workstream-1')).toEqual(expect.objectContaining({
|
|
94
|
+
id: 'workstream-1',
|
|
95
|
+
title: 'Delivery',
|
|
96
|
+
}));
|
|
97
|
+
expect(applyModel.taskMutations.upserts).toHaveLength(2);
|
|
98
|
+
expect(applyModel.taskMutations.deletes.get('task-1')).toBe('2026-04-10T11:00:00.000Z');
|
|
99
|
+
const core = createCoreFixture({
|
|
100
|
+
resolveTaskIdentifier: vi.fn().mockReturnValue({
|
|
101
|
+
id: 'task-local-7',
|
|
102
|
+
title: 'Local incumbent',
|
|
103
|
+
referenceNumber: 7,
|
|
104
|
+
}),
|
|
105
|
+
getTask: vi.fn().mockImplementation((taskId) => {
|
|
106
|
+
if (taskId === 'task-1') {
|
|
107
|
+
return {
|
|
108
|
+
id: 'task-1',
|
|
109
|
+
title: 'Superseded task',
|
|
110
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
111
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
112
|
+
isArchived: false,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (taskId === 'task-stale') {
|
|
116
|
+
return {
|
|
117
|
+
id: 'task-stale',
|
|
118
|
+
title: 'Keep local task',
|
|
119
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
120
|
+
updatedAt: '2026-04-10T09:30:00.000Z',
|
|
121
|
+
isArchived: false,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
const prepared = prepareTaskSyncSnapshotApply({
|
|
128
|
+
core,
|
|
129
|
+
workspaceId,
|
|
130
|
+
taskMutations: applyModel.taskMutations,
|
|
131
|
+
recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
|
|
132
|
+
diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
expect(prepared.tasks).toEqual([]);
|
|
136
|
+
expect(prepared.archived).toEqual([
|
|
137
|
+
expect.objectContaining({
|
|
138
|
+
id: 'task-2',
|
|
139
|
+
title: 'Archived task',
|
|
140
|
+
}),
|
|
141
|
+
]);
|
|
142
|
+
expect(diagnostics).toEqual(expect.arrayContaining([
|
|
143
|
+
expect.objectContaining({
|
|
144
|
+
workspaceId,
|
|
145
|
+
eventType: 'apply',
|
|
146
|
+
message: 'Detected task reference number mismatch during normal sync.',
|
|
147
|
+
details: expect.objectContaining({
|
|
148
|
+
referenceLabel: 'T-7',
|
|
149
|
+
existingTaskId: 'task-local-7',
|
|
150
|
+
taskId: 'task-1',
|
|
151
|
+
}),
|
|
152
|
+
}),
|
|
153
|
+
]));
|
|
154
|
+
const deleteCount = applyTaskSyncDeletes({
|
|
155
|
+
core,
|
|
156
|
+
workspaceId,
|
|
157
|
+
taskMutations: applyModel.taskMutations,
|
|
158
|
+
recordSyncDiagnostic: vi.fn(),
|
|
159
|
+
});
|
|
160
|
+
expect(deleteCount).toBe(2);
|
|
161
|
+
expect(core.deleteTaskForSync).toHaveBeenCalledTimes(1);
|
|
162
|
+
expect(core.deleteTaskForSync).toHaveBeenCalledWith('task-1', workspaceId, { persistBackup: false });
|
|
163
|
+
const result = applyPreparedTaskSyncSnapshot({
|
|
164
|
+
core,
|
|
165
|
+
workspaceId,
|
|
166
|
+
prepared,
|
|
167
|
+
});
|
|
168
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
|
|
169
|
+
tasks: [],
|
|
170
|
+
archived: [
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
id: 'task-2',
|
|
173
|
+
title: 'Archived task',
|
|
174
|
+
isArchived: true,
|
|
175
|
+
}),
|
|
176
|
+
],
|
|
177
|
+
}, workspaceId);
|
|
178
|
+
expect(result).toEqual(expect.objectContaining({ total: 1 }));
|
|
179
|
+
});
|
|
180
|
+
it('applies document and asset changes through the content handler helpers', async () => {
|
|
181
|
+
const core = createCoreFixture();
|
|
182
|
+
const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
|
|
183
|
+
const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
|
|
184
|
+
const deleteWorkspaceSyncDocument = vi.fn();
|
|
185
|
+
const deleteWorkspaceSyncBinaryAsset = vi.fn();
|
|
186
|
+
await applyContentSyncChanges({
|
|
187
|
+
core,
|
|
188
|
+
workspaceId: 'workspace-1',
|
|
189
|
+
incomingDocuments: [
|
|
190
|
+
{
|
|
191
|
+
path: 'docs/spec.md',
|
|
192
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
193
|
+
content: '# Spec',
|
|
194
|
+
assetId: 'asset-doc-1',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
incomingDocumentDeletes: new Map([
|
|
198
|
+
['docs/stale.md', '2026-04-10T11:00:00.000Z'],
|
|
199
|
+
]),
|
|
200
|
+
incomingAssets: [
|
|
201
|
+
{
|
|
202
|
+
path: 'assets/proof.png',
|
|
203
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
204
|
+
contentBase64: Buffer.from('png-bytes').toString('base64'),
|
|
205
|
+
assetId: 'asset-image-1',
|
|
206
|
+
kind: 'image',
|
|
207
|
+
mimeType: 'image/png',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
incomingAssetDeletes: new Map([
|
|
211
|
+
['assets/stale.png', '2026-04-10T11:05:00.000Z'],
|
|
212
|
+
]),
|
|
213
|
+
basePath: '/tmp/taskforce-sync-content',
|
|
214
|
+
hasObjectStorageClient: () => false,
|
|
215
|
+
hasDocumentSyncMetadataDrift: () => false,
|
|
216
|
+
hasBinaryAssetSyncMetadataDrift: () => false,
|
|
217
|
+
getExistingDocument: () => null,
|
|
218
|
+
writeWorkspaceSyncDocument,
|
|
219
|
+
deleteWorkspaceSyncDocument,
|
|
220
|
+
writeWorkspaceSyncBinaryAsset,
|
|
221
|
+
deleteWorkspaceSyncBinaryAsset,
|
|
222
|
+
getExistingBinaryAsset: () => null,
|
|
223
|
+
recordSyncDiagnostic: vi.fn(),
|
|
224
|
+
});
|
|
225
|
+
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md', assetId: 'asset-doc-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
226
|
+
expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png', assetId: 'asset-image-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
227
|
+
expect(deleteWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'docs/stale.md', 'workspace-1');
|
|
228
|
+
expect(deleteWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'assets/stale.png', 'workspace-1');
|
|
229
|
+
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
|
|
230
|
+
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
|
|
231
|
+
});
|
|
232
|
+
it('only allows reference reassignment for content writes when the content changed or repair is explicit', async () => {
|
|
233
|
+
const core = createCoreFixture({
|
|
234
|
+
getDocumentWatermark: vi.fn().mockReturnValue(null),
|
|
235
|
+
});
|
|
236
|
+
const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
|
|
237
|
+
const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
|
|
238
|
+
await applyContentSyncChanges({
|
|
239
|
+
core,
|
|
240
|
+
workspaceId: 'workspace-1',
|
|
241
|
+
incomingDocuments: [
|
|
242
|
+
{
|
|
243
|
+
path: 'docs/spec.md',
|
|
244
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
245
|
+
content: '# Spec',
|
|
246
|
+
assetId: 'asset-doc-1',
|
|
247
|
+
referenceNumber: 22,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
incomingDocumentDeletes: new Map(),
|
|
251
|
+
incomingAssets: [
|
|
252
|
+
{
|
|
253
|
+
path: 'assets/proof.png',
|
|
254
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
255
|
+
contentBase64: Buffer.from('new-bytes').toString('base64'),
|
|
256
|
+
assetId: 'asset-image-1',
|
|
257
|
+
kind: 'image',
|
|
258
|
+
mimeType: 'image/png',
|
|
259
|
+
referenceNumber: 33,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
incomingAssetDeletes: new Map(),
|
|
263
|
+
basePath: '/tmp/taskforce-sync-content',
|
|
264
|
+
hasObjectStorageClient: () => false,
|
|
265
|
+
hasDocumentSyncMetadataDrift: () => true,
|
|
266
|
+
hasBinaryAssetSyncMetadataDrift: () => true,
|
|
267
|
+
getExistingDocument: () => ({
|
|
268
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
269
|
+
contentHash: '6bb50a17e2dc2fc14caea95a15cd8572018b6d22d74fb7b9ad015b5a0a8a935e',
|
|
270
|
+
}),
|
|
271
|
+
writeWorkspaceSyncDocument,
|
|
272
|
+
deleteWorkspaceSyncDocument: vi.fn(),
|
|
273
|
+
writeWorkspaceSyncBinaryAsset,
|
|
274
|
+
deleteWorkspaceSyncBinaryAsset: vi.fn(),
|
|
275
|
+
getExistingBinaryAsset: () => ({
|
|
276
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
277
|
+
contentSha256: 'old-hash',
|
|
278
|
+
}),
|
|
279
|
+
recordSyncDiagnostic: vi.fn(),
|
|
280
|
+
});
|
|
281
|
+
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: false });
|
|
282
|
+
expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
283
|
+
});
|
|
284
|
+
it('applies collaboration upserts and deletes through the collaboration handlers', () => {
|
|
285
|
+
const core = createCoreFixture();
|
|
286
|
+
applyCollaborationSyncChanges({
|
|
287
|
+
core,
|
|
288
|
+
workspaceId: 'workspace-1',
|
|
289
|
+
incomingDocumentReviewSessions: new Map([
|
|
290
|
+
['session-1', {
|
|
291
|
+
id: 'session-1',
|
|
292
|
+
assetId: 'asset-doc-1',
|
|
293
|
+
documentId: 'doc-1',
|
|
294
|
+
documentVersion: 3,
|
|
295
|
+
title: 'Review round',
|
|
296
|
+
status: 'open',
|
|
297
|
+
comments: [],
|
|
298
|
+
createdByActorId: 'user-1',
|
|
299
|
+
updatedByActorId: 'user-1',
|
|
300
|
+
createdAt: '2026-04-10T09:00:00.000Z',
|
|
301
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
302
|
+
deletedAt: null,
|
|
303
|
+
}],
|
|
304
|
+
]),
|
|
305
|
+
incomingDocumentReviewSessionDeletes: new Map([
|
|
306
|
+
['session-2', '2026-04-10T11:00:00.000Z'],
|
|
307
|
+
]),
|
|
308
|
+
incomingDocumentReviewComments: new Map([
|
|
309
|
+
['comment-1', {
|
|
310
|
+
id: 'comment-1',
|
|
311
|
+
sessionId: 'session-1',
|
|
312
|
+
body: 'Looks good',
|
|
313
|
+
anchor: { kind: 'document' },
|
|
314
|
+
order: 0,
|
|
315
|
+
authorActorId: 'user-1',
|
|
316
|
+
createdAt: '2026-04-10T09:05:00.000Z',
|
|
317
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
318
|
+
deletedAt: null,
|
|
319
|
+
}],
|
|
320
|
+
]),
|
|
321
|
+
incomingDocumentReviewCommentDeletes: new Map([
|
|
322
|
+
['comment-2', '2026-04-10T11:05:00.000Z'],
|
|
323
|
+
]),
|
|
324
|
+
incomingAnnotatedAttachmentSessions: new Map([
|
|
325
|
+
['annotation-session-1', {
|
|
326
|
+
id: 'annotation-session-1',
|
|
327
|
+
workspaceId: 'workspace-1',
|
|
328
|
+
taskId: 'task-1',
|
|
329
|
+
baseImageAssetId: 'asset-image-1',
|
|
330
|
+
title: 'Annotations',
|
|
331
|
+
globalInstruction: 'Inspect issue',
|
|
332
|
+
annotations: [],
|
|
333
|
+
createdByActorId: 'user-1',
|
|
334
|
+
updatedByActorId: 'user-1',
|
|
335
|
+
createdAt: '2026-04-10T09:10:00.000Z',
|
|
336
|
+
updatedAt: '2026-04-10T10:10:00.000Z',
|
|
337
|
+
deletedAt: null,
|
|
338
|
+
}],
|
|
339
|
+
]),
|
|
340
|
+
incomingAnnotatedAttachmentSessionDeletes: new Map([
|
|
341
|
+
['annotation-session-2', '2026-04-10T11:10:00.000Z'],
|
|
342
|
+
]),
|
|
343
|
+
recordSyncDiagnostic: vi.fn(),
|
|
344
|
+
createSyncRouteError: (message, details) => Object.assign(new Error(message), { details }),
|
|
345
|
+
});
|
|
346
|
+
expect(core.upsertDocumentReviewSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
347
|
+
id: 'session-1',
|
|
348
|
+
workspaceId: 'workspace-1',
|
|
349
|
+
assetId: 'asset-doc-1',
|
|
350
|
+
}));
|
|
351
|
+
expect(core.deleteDocumentReviewSessionForSync).toHaveBeenCalledWith({
|
|
352
|
+
workspaceId: 'workspace-1',
|
|
353
|
+
sessionId: 'session-2',
|
|
354
|
+
deletedAt: '2026-04-10T11:00:00.000Z',
|
|
355
|
+
});
|
|
356
|
+
expect(core.upsertDocumentReviewCommentForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
357
|
+
id: 'comment-1',
|
|
358
|
+
sessionId: 'session-1',
|
|
359
|
+
workspaceId: 'workspace-1',
|
|
360
|
+
}));
|
|
361
|
+
expect(core.deleteDocumentReviewCommentForSync).toHaveBeenCalledWith({
|
|
362
|
+
workspaceId: 'workspace-1',
|
|
363
|
+
commentId: 'comment-2',
|
|
364
|
+
deletedAt: '2026-04-10T11:05:00.000Z',
|
|
365
|
+
});
|
|
366
|
+
expect(core.upsertAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
367
|
+
id: 'annotation-session-1',
|
|
368
|
+
workspaceId: 'workspace-1',
|
|
369
|
+
baseImageAssetId: 'asset-image-1',
|
|
370
|
+
}));
|
|
371
|
+
expect(core.deleteAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith({
|
|
372
|
+
workspaceId: 'workspace-1',
|
|
373
|
+
sessionId: 'annotation-session-2',
|
|
374
|
+
deletedAt: '2026-04-10T11:10:00.000Z',
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
@@ -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;
|
|
@@ -77,10 +77,16 @@ export interface WorkspacePushSyncResult {
|
|
|
77
77
|
currentAnnotatedAttachmentSessionIds: Set<string>;
|
|
78
78
|
deleteAnnotatedAttachmentSessionIds: Set<string>;
|
|
79
79
|
currentAnnotatedAttachmentSessionWatermarks: Map<string, string>;
|
|
80
|
+
currentTaskEventWatermarks: Map<string, string>;
|
|
80
81
|
/** Watermarks for every task upsert that was successfully pushed. */
|
|
81
82
|
pushedWatermarks: Map<string, string>;
|
|
82
83
|
/** Server-emitted realtime mutation event ids for this push apply. */
|
|
83
84
|
emittedEventIds: string[];
|
|
85
|
+
/** Authoritative task snapshots returned immediately by the push route for incoming task upserts. */
|
|
86
|
+
appliedTaskUpserts: Array<{
|
|
87
|
+
archived: boolean;
|
|
88
|
+
task: WorkspaceSyncTaskSnapshot;
|
|
89
|
+
}>;
|
|
84
90
|
}
|
|
85
91
|
export declare function runWorkspacePushSyncService(input: WorkspacePushSyncInput): Promise<WorkspacePushSyncResult>;
|
|
86
92
|
export type WorkspacePullFailureKind = 'pull_http' | 'apply_auth' | 'apply_all_candidates' | 'apply_payload' | 'exception';
|
|
@@ -113,8 +119,10 @@ export interface WorkspacePullSyncResult {
|
|
|
113
119
|
pulledDeleteTaskIds: Set<string>;
|
|
114
120
|
pulledActiveUpserts: boolean;
|
|
115
121
|
pulledArchivedUpserts: boolean;
|
|
122
|
+
pulledTaskEventUpserts?: boolean;
|
|
116
123
|
documentDecryptFailures?: string[];
|
|
117
124
|
emittedEventIds?: string[];
|
|
125
|
+
serverDiagnostics?: Record<string, unknown> | null;
|
|
118
126
|
}
|
|
119
127
|
export declare function runWorkspacePullSyncService(input: WorkspacePullSyncInput): Promise<WorkspacePullSyncResult>;
|
|
120
128
|
export declare function syncUserGlobalSettingsService(input: UserSettingsSyncInput): Promise<UserSettingsSyncResult>;
|
package/dist/sync/syncService.js
CHANGED
|
@@ -187,8 +187,10 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
187
187
|
currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
|
|
188
188
|
deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
|
|
189
189
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
190
|
+
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
190
191
|
pushedWatermarks: new Map(),
|
|
191
|
-
emittedEventIds: []
|
|
192
|
+
emittedEventIds: [],
|
|
193
|
+
appliedTaskUpserts: []
|
|
192
194
|
};
|
|
193
195
|
}
|
|
194
196
|
const pushData = asObjectRecord(pushResult.data);
|
|
@@ -197,6 +199,23 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
197
199
|
.map((entry) => String(entry || '').trim())
|
|
198
200
|
.filter(Boolean)))
|
|
199
201
|
: [];
|
|
202
|
+
const appliedTaskUpserts = Array.isArray(pushData.appliedTaskUpserts)
|
|
203
|
+
? pushData.appliedTaskUpserts
|
|
204
|
+
.map((entry) => {
|
|
205
|
+
const source = entry && typeof entry === 'object' ? entry : {};
|
|
206
|
+
const task = source.task && typeof source.task === 'object'
|
|
207
|
+
? source.task
|
|
208
|
+
: null;
|
|
209
|
+
const taskId = String(task?.id || '').trim();
|
|
210
|
+
if (!task || !taskId)
|
|
211
|
+
return null;
|
|
212
|
+
return {
|
|
213
|
+
archived: source.archived === true,
|
|
214
|
+
task
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
.filter((entry) => Boolean(entry))
|
|
218
|
+
: [];
|
|
200
219
|
return {
|
|
201
220
|
success: true,
|
|
202
221
|
syncedAt: new Date().toISOString(),
|
|
@@ -226,8 +245,10 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
226
245
|
currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
|
|
227
246
|
deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
|
|
228
247
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
248
|
+
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
229
249
|
pushedWatermarks: input.payload.pushedWatermarks,
|
|
230
|
-
emittedEventIds
|
|
250
|
+
emittedEventIds,
|
|
251
|
+
appliedTaskUpserts
|
|
231
252
|
};
|
|
232
253
|
}
|
|
233
254
|
export async function runWorkspacePullSyncService(input) {
|
|
@@ -240,9 +261,11 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
240
261
|
const pulledDeleteTaskIds = new Set();
|
|
241
262
|
let pulledActiveUpserts = false;
|
|
242
263
|
let pulledArchivedUpserts = false;
|
|
264
|
+
let pulledTaskEventUpserts = false;
|
|
243
265
|
const documentDecryptFailures = new Set();
|
|
244
266
|
const emittedEventIds = new Set();
|
|
245
267
|
const pulledAnnotatedAttachmentSessionIds = new Set();
|
|
268
|
+
let lastServerDiagnostics = null;
|
|
246
269
|
const maxPages = Number.isFinite(Number(input.maxPages)) ? Math.max(1, Math.floor(Number(input.maxPages))) : 20;
|
|
247
270
|
try {
|
|
248
271
|
while (hasMore && pages < maxPages) {
|
|
@@ -279,11 +302,17 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
279
302
|
pulledDeleteTaskIds,
|
|
280
303
|
pulledActiveUpserts,
|
|
281
304
|
pulledArchivedUpserts,
|
|
282
|
-
|
|
305
|
+
pulledTaskEventUpserts,
|
|
306
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
307
|
+
serverDiagnostics: null
|
|
283
308
|
};
|
|
284
309
|
}
|
|
285
310
|
const pullData = pullRes.data || {};
|
|
286
311
|
const changes = Array.isArray(pullData?.changes) ? pullData.changes : [];
|
|
312
|
+
const serverDiagnostics = pullData?.diagnostics && typeof pullData.diagnostics === 'object'
|
|
313
|
+
? pullData.diagnostics
|
|
314
|
+
: null;
|
|
315
|
+
lastServerDiagnostics = serverDiagnostics;
|
|
287
316
|
const hasWorkspaceMembersSnapshot = Array.isArray(pullData?.workspaceMembers);
|
|
288
317
|
const workspaceMembers = hasWorkspaceMembersSnapshot
|
|
289
318
|
? pullData.workspaceMembers
|
|
@@ -303,6 +332,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
303
332
|
pulledAnnotatedAttachmentSessionIds.add(sessionId);
|
|
304
333
|
continue;
|
|
305
334
|
}
|
|
335
|
+
if (change.op === 'task-event-upsert') {
|
|
336
|
+
pulledTaskEventUpserts = true;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
306
339
|
if (change.op === 'document-upsert') {
|
|
307
340
|
if (typeof change.content === 'string' && typeof process !== 'undefined' && process.env?.TASKFORCE_SYNC_KEY) {
|
|
308
341
|
try {
|
|
@@ -365,7 +398,9 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
365
398
|
pulledDeleteTaskIds,
|
|
366
399
|
pulledActiveUpserts,
|
|
367
400
|
pulledArchivedUpserts,
|
|
368
|
-
|
|
401
|
+
pulledTaskEventUpserts,
|
|
402
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
403
|
+
serverDiagnostics
|
|
369
404
|
};
|
|
370
405
|
}
|
|
371
406
|
const hasTransientApplyFailure = applyFailures.some((entry) => {
|
|
@@ -391,8 +426,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
391
426
|
pulledDeleteTaskIds,
|
|
392
427
|
pulledActiveUpserts,
|
|
393
428
|
pulledArchivedUpserts,
|
|
429
|
+
pulledTaskEventUpserts,
|
|
394
430
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
395
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
431
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
432
|
+
serverDiagnostics: lastServerDiagnostics
|
|
396
433
|
};
|
|
397
434
|
}
|
|
398
435
|
const applyPayloadData = asObjectRecord(applyResult.data);
|
|
@@ -411,8 +448,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
411
448
|
pulledDeleteTaskIds,
|
|
412
449
|
pulledActiveUpserts,
|
|
413
450
|
pulledArchivedUpserts,
|
|
451
|
+
pulledTaskEventUpserts,
|
|
414
452
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
415
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
453
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
454
|
+
serverDiagnostics: lastServerDiagnostics
|
|
416
455
|
};
|
|
417
456
|
}
|
|
418
457
|
applyMs += (Date.now() - applyStartedAtMs);
|
|
@@ -460,8 +499,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
460
499
|
pulledDeleteTaskIds,
|
|
461
500
|
pulledActiveUpserts,
|
|
462
501
|
pulledArchivedUpserts,
|
|
502
|
+
pulledTaskEventUpserts,
|
|
463
503
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
464
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
504
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
505
|
+
serverDiagnostics: lastServerDiagnostics
|
|
465
506
|
};
|
|
466
507
|
}
|
|
467
508
|
const hasTransientApplyFailure = bootstrapApplyFailures.some((entry) => {
|
|
@@ -487,8 +528,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
487
528
|
pulledDeleteTaskIds,
|
|
488
529
|
pulledActiveUpserts,
|
|
489
530
|
pulledArchivedUpserts,
|
|
531
|
+
pulledTaskEventUpserts,
|
|
490
532
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
491
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
533
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
534
|
+
serverDiagnostics: lastServerDiagnostics
|
|
492
535
|
};
|
|
493
536
|
}
|
|
494
537
|
applyMs += (Date.now() - bootstrapApplyStartedAtMs);
|
|
@@ -504,8 +547,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
504
547
|
pulledDeleteTaskIds,
|
|
505
548
|
pulledActiveUpserts,
|
|
506
549
|
pulledArchivedUpserts,
|
|
550
|
+
pulledTaskEventUpserts,
|
|
507
551
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
508
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
552
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
553
|
+
serverDiagnostics: lastServerDiagnostics
|
|
509
554
|
};
|
|
510
555
|
}
|
|
511
556
|
catch (error) {
|
|
@@ -523,8 +568,10 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
523
568
|
pulledDeleteTaskIds,
|
|
524
569
|
pulledActiveUpserts,
|
|
525
570
|
pulledArchivedUpserts,
|
|
571
|
+
pulledTaskEventUpserts,
|
|
526
572
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
527
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
573
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
574
|
+
serverDiagnostics: null
|
|
528
575
|
};
|
|
529
576
|
}
|
|
530
577
|
}
|
|
@@ -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,
|
|
@@ -15,6 +15,17 @@ export interface WorkspacePullFeedResult {
|
|
|
15
15
|
changes: EncodedWorkspaceSyncChange[];
|
|
16
16
|
nextCursor: string | null;
|
|
17
17
|
hasMore: boolean;
|
|
18
|
+
diagnostics: {
|
|
19
|
+
totalEntries: number;
|
|
20
|
+
pageEntries: number;
|
|
21
|
+
skippedHydrationEntries: number;
|
|
22
|
+
documentIndexCount: number;
|
|
23
|
+
assetIndexCount: number;
|
|
24
|
+
indexMs: number;
|
|
25
|
+
hydrationMs: number;
|
|
26
|
+
encodeMs: number;
|
|
27
|
+
buildMs: number;
|
|
28
|
+
};
|
|
18
29
|
}
|
|
19
30
|
export declare function buildWorkspacePullFeed(input: {
|
|
20
31
|
core: SyncCore;
|