@taskforcehq/taskforce 0.3.303 → 0.3.304
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- 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/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 +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- 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 +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -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 +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 +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -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 +174 -476
- package/dist/mcp/runtime.test.js +81 -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/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.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- 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 +12 -10
- 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 +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -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 +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-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.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-CGk9s3NJ.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-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +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
|
@@ -211,6 +211,18 @@ function setupBillingSchema(db) {
|
|
|
211
211
|
function buildBillingRouteHandlers(db, opts) {
|
|
212
212
|
const routes = new Map();
|
|
213
213
|
const tenantId = 'default';
|
|
214
|
+
const resolvePlanIdForVersion = (planVersionIdRaw) => {
|
|
215
|
+
const planVersionId = String(planVersionIdRaw || '').trim();
|
|
216
|
+
if (!planVersionId)
|
|
217
|
+
return '';
|
|
218
|
+
const row = db.prepare(`
|
|
219
|
+
SELECT plan_id
|
|
220
|
+
FROM plan_versions
|
|
221
|
+
WHERE tenant_id = ? AND plan_version_id = ?
|
|
222
|
+
LIMIT 1
|
|
223
|
+
`).get(tenantId, planVersionId);
|
|
224
|
+
return String(row?.plan_id || '').trim();
|
|
225
|
+
};
|
|
214
226
|
const core = {
|
|
215
227
|
getDatabaseAdapter: () => db,
|
|
216
228
|
getTenantId: () => tenantId,
|
|
@@ -223,6 +235,37 @@ function buildBillingRouteHandlers(db, opts) {
|
|
|
223
235
|
ON CONFLICT (user_id) DO NOTHING
|
|
224
236
|
`).run(userId, tenantId, now, now);
|
|
225
237
|
},
|
|
238
|
+
transitionCanonicalCommercialState: (input) => {
|
|
239
|
+
const now = new Date().toISOString();
|
|
240
|
+
const planVersionId = String(input.planVersionId || '').trim();
|
|
241
|
+
const effectiveAt = input.effectiveAt || now;
|
|
242
|
+
const effectiveUntil = input.effectiveUntil || null;
|
|
243
|
+
db.prepare(`
|
|
244
|
+
INSERT INTO account_entitlements (
|
|
245
|
+
account_id, tenant_id, plan_version_id, entitlement_state, effective_at, effective_until, updated_by, created_at, updated_at
|
|
246
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
247
|
+
ON CONFLICT (account_id) DO UPDATE SET
|
|
248
|
+
plan_version_id = excluded.plan_version_id,
|
|
249
|
+
entitlement_state = excluded.entitlement_state,
|
|
250
|
+
effective_at = excluded.effective_at,
|
|
251
|
+
effective_until = excluded.effective_until,
|
|
252
|
+
updated_by = excluded.updated_by,
|
|
253
|
+
updated_at = excluded.updated_at
|
|
254
|
+
`).run(input.accountId, tenantId, planVersionId, input.state, effectiveAt, effectiveUntil, input.updatedBy, input.createdAt || now, input.updatedAt || now);
|
|
255
|
+
const planId = typeof input.billingMirrorPlanId === 'string'
|
|
256
|
+
? input.billingMirrorPlanId
|
|
257
|
+
: resolvePlanIdForVersion(planVersionId);
|
|
258
|
+
db.prepare(`
|
|
259
|
+
INSERT INTO user_billing (
|
|
260
|
+
user_id, tenant_id, tier, entitlement_state, effective_until, created_at, updated_at
|
|
261
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
262
|
+
ON CONFLICT (user_id) DO UPDATE SET
|
|
263
|
+
tier = excluded.tier,
|
|
264
|
+
entitlement_state = excluded.entitlement_state,
|
|
265
|
+
effective_until = excluded.effective_until,
|
|
266
|
+
updated_at = excluded.updated_at
|
|
267
|
+
`).run(input.accountId, tenantId, planId, input.state, effectiveUntil, now, now);
|
|
268
|
+
},
|
|
226
269
|
listPlanCatalog: () => [
|
|
227
270
|
{ planId: 'pro', displayName: 'Pro Plan', status: 'active', createdAt: '', updatedAt: '' },
|
|
228
271
|
{ planId: 'inactive-plan', displayName: 'Inactive', status: 'archived', createdAt: '', updatedAt: '' },
|
|
@@ -7,8 +7,38 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
9
9
|
import { TaskforceCore } from '../../core/Taskforce.js';
|
|
10
|
+
import type { McpAccessTokenAuthResult, McpOAuthAccessTokenAuthResult, McpAccessTokenScope } from '../../core/Taskforce.js';
|
|
10
11
|
import type { RouteContext } from '../routes.js';
|
|
12
|
+
import type { AiProfileBootstrapConfig } from '../../mcp/aiProfileBootstrap.js';
|
|
11
13
|
export declare function resolveErrorStatusCode(error: unknown, fallback?: number): number;
|
|
14
|
+
export declare function buildCloudMcpRuntimeAuth(input: {
|
|
15
|
+
req: IncomingMessage;
|
|
16
|
+
auth: {
|
|
17
|
+
authenticatedViaToken: McpAccessTokenAuthResult | null;
|
|
18
|
+
authenticatedViaOAuth: McpOAuthAccessTokenAuthResult | null;
|
|
19
|
+
userId: string | null;
|
|
20
|
+
} & Required<AiProfileBootstrapConfig>;
|
|
21
|
+
requestHeader: (req: IncomingMessage, name: string, fallback?: string) => string;
|
|
22
|
+
requestId?: string | null;
|
|
23
|
+
authScopes?: McpAccessTokenScope[] | null;
|
|
24
|
+
}): {
|
|
25
|
+
scopes?: McpAccessTokenScope[] | null;
|
|
26
|
+
userId?: string | null;
|
|
27
|
+
tokenId?: string | null;
|
|
28
|
+
aiProfileToken?: AiProfileBootstrapConfig['aiProfileToken'];
|
|
29
|
+
aiProfileName?: AiProfileBootstrapConfig['aiProfileName'];
|
|
30
|
+
aiProfileIcon?: AiProfileBootstrapConfig['aiProfileIcon'];
|
|
31
|
+
aiProfileColor?: AiProfileBootstrapConfig['aiProfileColor'];
|
|
32
|
+
aiProfileDescription?: AiProfileBootstrapConfig['aiProfileDescription'];
|
|
33
|
+
aiProfileRole?: AiProfileBootstrapConfig['aiProfileRole'];
|
|
34
|
+
aiProfileProvider?: AiProfileBootstrapConfig['aiProfileProvider'];
|
|
35
|
+
aiProfileModel?: AiProfileBootstrapConfig['aiProfileModel'];
|
|
36
|
+
aiProfileSurfaceType?: AiProfileBootstrapConfig['aiProfileSurfaceType'];
|
|
37
|
+
actorType?: 'local' | 'user' | 'token' | 'oauth';
|
|
38
|
+
requestId?: string | null;
|
|
39
|
+
ip?: string | null;
|
|
40
|
+
userAgent?: string | null;
|
|
41
|
+
};
|
|
12
42
|
export declare function requestHeader(req: IncomingMessage, name: string, fallback?: string): string;
|
|
13
43
|
export declare function requestWorkspaceHeader(req: IncomingMessage): string;
|
|
14
44
|
export declare function requestAuthoritativeWorkspaceId(req: IncomingMessage): string;
|
|
@@ -17,6 +17,32 @@ export function resolveErrorStatusCode(error, fallback = 400) {
|
|
|
17
17
|
}
|
|
18
18
|
return fallback;
|
|
19
19
|
}
|
|
20
|
+
export function buildCloudMcpRuntimeAuth(input) {
|
|
21
|
+
const { req, auth, requestHeader, requestId, authScopes } = input;
|
|
22
|
+
const actorType = auth.authenticatedViaToken
|
|
23
|
+
? 'token'
|
|
24
|
+
: auth.authenticatedViaOAuth
|
|
25
|
+
? 'oauth'
|
|
26
|
+
: 'user';
|
|
27
|
+
return {
|
|
28
|
+
scopes: authScopes || auth.authenticatedViaToken?.scopes || auth.authenticatedViaOAuth?.scopes || [],
|
|
29
|
+
userId: auth.authenticatedViaToken?.userId || auth.authenticatedViaOAuth?.userId || auth.userId || null,
|
|
30
|
+
tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
|
|
31
|
+
aiProfileToken: auth.aiProfileToken,
|
|
32
|
+
aiProfileName: auth.aiProfileName,
|
|
33
|
+
aiProfileIcon: auth.aiProfileIcon,
|
|
34
|
+
aiProfileColor: auth.aiProfileColor,
|
|
35
|
+
aiProfileDescription: auth.aiProfileDescription,
|
|
36
|
+
aiProfileRole: auth.aiProfileRole,
|
|
37
|
+
aiProfileProvider: auth.aiProfileProvider,
|
|
38
|
+
aiProfileModel: auth.aiProfileModel,
|
|
39
|
+
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
40
|
+
actorType,
|
|
41
|
+
requestId: String(requestId || req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || undefined,
|
|
42
|
+
ip: req.socket?.remoteAddress || null,
|
|
43
|
+
userAgent: requestHeader(req, 'user-agent', '') || null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
20
46
|
// === SECTION: Request header helpers ===
|
|
21
47
|
export function requestHeader(req, name, fallback = '') {
|
|
22
48
|
const value = req.headers?.[name.toLowerCase()];
|
|
@@ -344,6 +344,10 @@ describe('Server Routes Workspace Sync Integration', () => {
|
|
|
344
344
|
username: 'codex',
|
|
345
345
|
icon: 'Bot',
|
|
346
346
|
color: '#0ea5e9',
|
|
347
|
+
description: 'Terminal-first coding agent',
|
|
348
|
+
role: 'Implementer',
|
|
349
|
+
provider: 'OpenAI',
|
|
350
|
+
model: 'gpt-5.4',
|
|
347
351
|
surfaceType: 'coding_tool',
|
|
348
352
|
createdAt: '2026-03-22T10:00:00.000Z',
|
|
349
353
|
updatedAt: '2026-03-22T10:05:00.000Z',
|
|
@@ -361,6 +365,10 @@ describe('Server Routes Workspace Sync Integration', () => {
|
|
|
361
365
|
id: 'ai-profile-codex',
|
|
362
366
|
name: 'Codex',
|
|
363
367
|
username: 'codex',
|
|
368
|
+
description: 'Terminal-first coding agent',
|
|
369
|
+
role: 'Implementer',
|
|
370
|
+
provider: 'OpenAI',
|
|
371
|
+
model: 'gpt-5.4',
|
|
364
372
|
surfaceType: 'coding_tool'
|
|
365
373
|
})
|
|
366
374
|
]);
|
|
@@ -1757,6 +1765,18 @@ describe('Server Routes Workspace Sync Integration', () => {
|
|
|
1757
1765
|
const pushRes = createMockRes();
|
|
1758
1766
|
await pushMatch?.handler(pushReq, pushRes, {});
|
|
1759
1767
|
expect(pushRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
1768
|
+
const pushBody = readJsonBody(pushRes);
|
|
1769
|
+
expect(Array.isArray(pushBody?.appliedTaskUpserts)).toBe(true);
|
|
1770
|
+
expect(pushBody?.appliedTaskUpserts).toEqual(expect.arrayContaining([
|
|
1771
|
+
expect.objectContaining({
|
|
1772
|
+
archived: false,
|
|
1773
|
+
task: expect.objectContaining({
|
|
1774
|
+
id: taskId,
|
|
1775
|
+
title: 'Offline provisional task',
|
|
1776
|
+
referenceNumber: 1
|
|
1777
|
+
})
|
|
1778
|
+
})
|
|
1779
|
+
]));
|
|
1760
1780
|
const persisted = core.getTask(taskId, workspaceId);
|
|
1761
1781
|
expect(persisted?.referenceNumber).toBe(1);
|
|
1762
1782
|
expect(Date.parse(String(persisted?.updatedAt || ''))).toBeGreaterThan(Date.parse(staleUpdatedAt));
|
|
@@ -5,15 +5,14 @@ import { WorkspaceAssetStore, } from '../../storage/workspaceAssetStore.js';
|
|
|
5
5
|
import { resolveSessionCookieAttributesForRequest } from '../auth.js';
|
|
6
6
|
import { decryptDocument, ENCRYPTION_PREFIX } from '../../sync/encryptionService.js';
|
|
7
7
|
import { isAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { normalizeBinaryAssetDeleteSyncPayload, normalizeBinaryAssetSyncPayload, normalizeDocumentDeleteSyncPayload, normalizeDocumentSyncPayload, serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload } from '../../sync/contentSyncPayload.js';
|
|
8
|
+
import { serializeTaskForSync, } from '../../sync/taskSyncPayload.js';
|
|
9
|
+
import { serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload, } from '../../sync/contentSyncPayload.js';
|
|
11
10
|
import { createContentSyncState } from '../../sync/contentSyncState.js';
|
|
12
|
-
import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload,
|
|
11
|
+
import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from '../../sync/collaborationSyncPayload.js';
|
|
13
12
|
import { createCollaborationSyncState } from '../../sync/collaborationSyncState.js';
|
|
14
13
|
import { buildWorkspacePullFeed } from '../../sync/workspacePullFeed.js';
|
|
15
14
|
import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from '../../sync/planningSyncPayload.js';
|
|
16
|
-
import { applyAiProfileAndMemberSyncChanges, applyCollaborationSyncChanges, applyContentSyncChanges, applyPlanningSyncChanges,
|
|
15
|
+
import { applyAiProfileAndMemberSyncChanges, applyCollaborationSyncChanges, applyContentSyncChanges, applyPlanningSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskActivitySyncChanges, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from '../../sync/syncApplyHandlers.js';
|
|
17
16
|
import { applyWorkspaceRepair } from '../../sync/workspaceRepair.js';
|
|
18
17
|
function decodeWorkspaceSyncCursor(raw) {
|
|
19
18
|
const value = String(raw || '').trim();
|
|
@@ -60,6 +59,40 @@ function normalizeIncomingAiProfileSurfaceType(value, context) {
|
|
|
60
59
|
}
|
|
61
60
|
return normalized;
|
|
62
61
|
}
|
|
62
|
+
function normalizeTaskEventSyncPayload(raw, workspaceId, sourceWatermark) {
|
|
63
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
64
|
+
return null;
|
|
65
|
+
const source = raw;
|
|
66
|
+
const id = String(source.id || '').trim();
|
|
67
|
+
const taskId = String(source.taskId || source.task_id || '').trim();
|
|
68
|
+
const action = String(source.action || '').trim();
|
|
69
|
+
const actor = String(source.actor || 'system').trim() || 'system';
|
|
70
|
+
const createdAt = String(source.createdAt || source.created_at || sourceWatermark || '').trim();
|
|
71
|
+
if (!id || !taskId || !action || !Number.isFinite(Date.parse(createdAt)))
|
|
72
|
+
return null;
|
|
73
|
+
const actorType = source.actorType === 'ai' || source.actorType === 'human' || source.actorType === 'system'
|
|
74
|
+
? source.actorType
|
|
75
|
+
: source.actor_type === 'ai' || source.actor_type === 'human' || source.actor_type === 'system'
|
|
76
|
+
? source.actor_type
|
|
77
|
+
: actor.startsWith('ai-profile-')
|
|
78
|
+
? 'ai'
|
|
79
|
+
: actor === 'system'
|
|
80
|
+
? 'system'
|
|
81
|
+
: 'human';
|
|
82
|
+
const details = source.details && typeof source.details === 'object' && !Array.isArray(source.details)
|
|
83
|
+
? source.details
|
|
84
|
+
: {};
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
taskId,
|
|
88
|
+
workspaceId,
|
|
89
|
+
action,
|
|
90
|
+
actor,
|
|
91
|
+
actorType,
|
|
92
|
+
details,
|
|
93
|
+
createdAt
|
|
94
|
+
};
|
|
95
|
+
}
|
|
63
96
|
export function registerSyncRoutes(deps) {
|
|
64
97
|
const { addRoute, core, context, auditAuthAction, resolveErrorStatusCode, requestWorkspaceId, resolveRequestAccess, isReservedWorkspaceId, createSessionTokenCookie } = deps;
|
|
65
98
|
const readPushIdempotency = (workspaceId, key) => {
|
|
@@ -190,32 +223,7 @@ export function registerSyncRoutes(deps) {
|
|
|
190
223
|
workspaceDocumentSnapshotCache,
|
|
191
224
|
});
|
|
192
225
|
const collaborationSyncState = createCollaborationSyncState({ core });
|
|
193
|
-
const hasTaskReferenceNumberDrift = (workspaceId, task) => {
|
|
194
|
-
const taskId = String(task?.id || '').trim();
|
|
195
|
-
const incomingReferenceNumber = Number.isFinite(Number(task?.referenceNumber))
|
|
196
|
-
? Math.max(1, Math.floor(Number(task?.referenceNumber)))
|
|
197
|
-
: null;
|
|
198
|
-
if (!incomingReferenceNumber)
|
|
199
|
-
return false;
|
|
200
|
-
const existingTask = core.resolveTaskIdentifier(`T-${incomingReferenceNumber}`, workspaceId);
|
|
201
|
-
if (!existingTask || existingTask.id === taskId)
|
|
202
|
-
return false;
|
|
203
|
-
recordSyncDiagnostic(workspaceId, 'apply', 'Detected task reference number mismatch during normal sync.', {
|
|
204
|
-
source: 'tasks.referenceNumber.mismatch',
|
|
205
|
-
taskId: taskId || null,
|
|
206
|
-
taskTitle: typeof task?.title === 'string' ? task.title.trim() || null : null,
|
|
207
|
-
existingTaskId: existingTask.id,
|
|
208
|
-
existingTaskTitle: existingTask.title || null,
|
|
209
|
-
referenceLabel: `T-${incomingReferenceNumber}`,
|
|
210
|
-
existingReferenceNumber: existingTask.referenceNumber ?? incomingReferenceNumber,
|
|
211
|
-
incomingReferenceNumber
|
|
212
|
-
});
|
|
213
|
-
return true;
|
|
214
|
-
};
|
|
215
226
|
const dispatchWorkspaceSyncChanges = async (workspaceId, incomingChanges, options) => {
|
|
216
|
-
const tasks = [];
|
|
217
|
-
const archived = [];
|
|
218
|
-
const incomingTaskDeletes = new Map();
|
|
219
227
|
const emittedEventIds = new Set();
|
|
220
228
|
const unsubscribeRealtime = core.onRealtimeMutation((event) => {
|
|
221
229
|
const eventWorkspaceId = String(event?.workspaceId || '').trim();
|
|
@@ -225,224 +233,63 @@ export function registerSyncRoutes(deps) {
|
|
|
225
233
|
if (eventId)
|
|
226
234
|
emittedEventIds.add(eventId);
|
|
227
235
|
});
|
|
228
|
-
let incomingTaxonomies = null;
|
|
229
|
-
let incomingTaxonomyState = null;
|
|
230
|
-
let incomingTaxonomiesUpdatedAt = '';
|
|
231
|
-
const incomingInitiatives = new Map();
|
|
232
|
-
const incomingWorkstreams = new Map();
|
|
233
|
-
const incomingAiProfiles = new Map();
|
|
234
|
-
const incomingDocuments = [];
|
|
235
|
-
const incomingDocumentDeletes = new Map();
|
|
236
|
-
const incomingDocumentReviewSessions = new Map();
|
|
237
|
-
const incomingDocumentReviewSessionDeletes = new Map();
|
|
238
|
-
const incomingDocumentReviewComments = new Map();
|
|
239
|
-
const incomingDocumentReviewCommentDeletes = new Map();
|
|
240
|
-
const incomingAnnotatedAttachmentSessions = new Map();
|
|
241
|
-
const incomingAnnotatedAttachmentSessionDeletes = new Map();
|
|
242
|
-
const incomingAssets = [];
|
|
243
|
-
const incomingAssetDeletes = new Map();
|
|
244
236
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (taxonomy?.updatedAt)
|
|
255
|
-
incomingTaxonomiesUpdatedAt = taxonomy.updatedAt;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (op === 'initiative-upsert') {
|
|
259
|
-
const initiative = normalizeInitiativeSyncPayload(raw.initiative, String(raw.updatedAt || raw.watermark || '').trim());
|
|
260
|
-
if (initiative)
|
|
261
|
-
incomingInitiatives.set(initiative.id, initiative);
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (op === 'workstream-upsert') {
|
|
265
|
-
const workstream = normalizeWorkstreamSyncPayload(raw.workstream, String(raw.updatedAt || raw.watermark || '').trim());
|
|
266
|
-
if (workstream)
|
|
267
|
-
incomingWorkstreams.set(workstream.id, workstream);
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (op === 'ai-profile-upsert') {
|
|
271
|
-
const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, 'apply.aiProfileUpsert');
|
|
272
|
-
const profileId = String(profile?.id || '').trim();
|
|
273
|
-
if (profileId && profile) {
|
|
274
|
-
const profileWorkspaceId = String(profile.workspaceId || '').trim();
|
|
275
|
-
if (profileWorkspaceId && profileWorkspaceId !== workspaceId) {
|
|
276
|
-
recordSyncDiagnostic(workspaceId, 'apply', `Rejected ai-profile-upsert for profile ${profileId}: workspace mismatch (expected ${workspaceId}, got ${profileWorkspaceId}).`, {
|
|
277
|
-
source: 'apply.aiProfileWorkspaceMismatch',
|
|
278
|
-
workspaceId,
|
|
279
|
-
profileId,
|
|
280
|
-
expectedWorkspaceId: workspaceId,
|
|
281
|
-
receivedWorkspaceId: profileWorkspaceId
|
|
282
|
-
});
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
incomingAiProfiles.set(profileId, {
|
|
286
|
-
...profile,
|
|
287
|
-
workspaceId,
|
|
288
|
-
createdAt: profile.createdAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
289
|
-
updatedAt: profile.updatedAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (op === 'document-upsert') {
|
|
295
|
-
const document = normalizeDocumentSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
296
|
-
if (document)
|
|
297
|
-
incomingDocuments.push(document);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
if (op === 'document-delete') {
|
|
301
|
-
const deletedDocument = normalizeDocumentDeleteSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
302
|
-
if (deletedDocument)
|
|
303
|
-
incomingDocumentDeletes.set(deletedDocument.path, deletedDocument.deletedAt);
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (op === 'asset-upsert') {
|
|
307
|
-
const asset = normalizeBinaryAssetSyncPayload(raw, (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()));
|
|
308
|
-
if (asset)
|
|
309
|
-
incomingAssets.push(asset);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (op === 'asset-delete') {
|
|
313
|
-
const deletedAsset = normalizeBinaryAssetDeleteSyncPayload(raw, (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()));
|
|
314
|
-
if (deletedAsset)
|
|
315
|
-
incomingAssetDeletes.set(deletedAsset.path, deletedAsset.deletedAt);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
if (op === 'document-review-session-upsert') {
|
|
319
|
-
const session = normalizeDocumentReviewSessionSyncPayload(raw.session, String(raw.updatedAt || raw.watermark || '').trim());
|
|
320
|
-
if (session)
|
|
321
|
-
incomingDocumentReviewSessions.set(session.id, session);
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (op === 'document-review-session-delete') {
|
|
325
|
-
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
326
|
-
if (deletedSession)
|
|
327
|
-
incomingDocumentReviewSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (op === 'document-review-comment-upsert') {
|
|
331
|
-
const comment = normalizeDocumentReviewCommentSyncPayload(raw.comment, { sourceWatermark: String(raw.updatedAt || raw.watermark || '').trim() });
|
|
332
|
-
if (comment)
|
|
333
|
-
incomingDocumentReviewComments.set(comment.id, comment);
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (op === 'document-review-comment-delete') {
|
|
337
|
-
const deletedComment = normalizeDeleteSyncPayload(raw, 'commentId');
|
|
338
|
-
if (deletedComment)
|
|
339
|
-
incomingDocumentReviewCommentDeletes.set(deletedComment.id, deletedComment.deletedAt);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if (op === 'annotated-attachment-session-upsert') {
|
|
343
|
-
const session = normalizeAnnotatedAttachmentSessionSyncPayload(raw.session, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
|
|
344
|
-
if (session)
|
|
345
|
-
incomingAnnotatedAttachmentSessions.set(session.id, session);
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
if (op === 'annotated-attachment-session-delete') {
|
|
349
|
-
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
350
|
-
if (deletedSession)
|
|
351
|
-
incomingAnnotatedAttachmentSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (op !== 'delete')
|
|
355
|
-
continue;
|
|
356
|
-
const task = normalizeTaskSyncPayload(raw.task);
|
|
357
|
-
const deleteTaskId = String(raw.taskId || raw.id || task?.id || '').trim();
|
|
358
|
-
if (deleteTaskId) {
|
|
359
|
-
incomingTaskDeletes.set(deleteTaskId, String(raw.deletedAt || raw.watermark || new Date().toISOString()).trim());
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
for (const change of incomingChanges) {
|
|
363
|
-
const raw = change;
|
|
364
|
-
const op = String(raw.op || '').trim().toLowerCase();
|
|
365
|
-
if (op === 'delete'
|
|
366
|
-
|| op === 'initiative-upsert'
|
|
367
|
-
|| op === 'workstream-upsert'
|
|
368
|
-
|| op === 'document-upsert'
|
|
369
|
-
|| op === 'document-delete'
|
|
370
|
-
|| op === 'asset-upsert'
|
|
371
|
-
|| op === 'asset-delete'
|
|
372
|
-
|| op === 'document-review-session-upsert'
|
|
373
|
-
|| op === 'document-review-session-delete'
|
|
374
|
-
|| op === 'document-review-comment-upsert'
|
|
375
|
-
|| op === 'document-review-comment-delete'
|
|
376
|
-
|| op === 'annotated-attachment-session-upsert'
|
|
377
|
-
|| op === 'annotated-attachment-session-delete')
|
|
378
|
-
continue;
|
|
379
|
-
const task = normalizeTaskSyncPayload(raw.task);
|
|
380
|
-
if (!task)
|
|
381
|
-
continue;
|
|
382
|
-
const taskId = String(task.id || '').trim();
|
|
383
|
-
hasTaskReferenceNumberDrift(workspaceId, task);
|
|
384
|
-
const deleteWatermark = taskId ? String(incomingTaskDeletes.get(taskId) || '').trim() : '';
|
|
385
|
-
if (taskId && deleteWatermark) {
|
|
386
|
-
const compareAgainstDelete = resolveSyncConflictOrdering(deleteWatermark, String(task.updatedAt || task.createdAt || '').trim(), `delete:${taskId}`, getTaskMutationTieBreak(task, raw.archived === true || task?.isArchived === true));
|
|
387
|
-
if (compareAgainstDelete >= 0)
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
const markedArchived = raw.archived === true || task?.isArchived === true;
|
|
391
|
-
if (markedArchived)
|
|
392
|
-
archived.push(task);
|
|
393
|
-
else
|
|
394
|
-
tasks.push(task);
|
|
395
|
-
}
|
|
396
|
-
for (const [taskId, deleteWatermark] of incomingTaskDeletes.entries()) {
|
|
397
|
-
try {
|
|
398
|
-
const existingTask = core.getTask(taskId, workspaceId);
|
|
399
|
-
if (existingTask) {
|
|
400
|
-
const compareAgainstExisting = resolveSyncConflictOrdering(deleteWatermark, getTaskMutationWatermark(existingTask), `delete:${taskId}`, getTaskMutationTieBreak(existingTask, Boolean(existingTask.isArchived)));
|
|
401
|
-
if (compareAgainstExisting < 0)
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
core.deleteTaskForSync(taskId, workspaceId, { persistBackup: false });
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete task during sync apply.', {
|
|
408
|
-
source: 'apply.deleteTask',
|
|
409
|
-
taskId,
|
|
410
|
-
error: String(error?.message || error || '')
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
}
|
|
237
|
+
const applyModel = classifyWorkspaceSyncChanges({
|
|
238
|
+
workspaceId,
|
|
239
|
+
incomingChanges,
|
|
240
|
+
normalizeWorkspaceSyncDocumentPath,
|
|
241
|
+
normalizeTaskAssetPath: (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()),
|
|
242
|
+
normalizeIncomingAiProfileSurfaceType,
|
|
243
|
+
normalizeTaskEventSyncPayload,
|
|
244
|
+
recordSyncDiagnostic,
|
|
245
|
+
});
|
|
414
246
|
applyPlanningSyncChanges({
|
|
415
247
|
core,
|
|
416
248
|
workspaceId,
|
|
417
|
-
incomingInitiatives,
|
|
418
|
-
incomingWorkstreams,
|
|
419
|
-
incomingTaxonomies,
|
|
420
|
-
incomingTaxonomyState,
|
|
421
|
-
incomingTaxonomiesUpdatedAt,
|
|
249
|
+
incomingInitiatives: applyModel.planning.incomingInitiatives,
|
|
250
|
+
incomingWorkstreams: applyModel.planning.incomingWorkstreams,
|
|
251
|
+
incomingTaxonomies: applyModel.planning.incomingTaxonomies,
|
|
252
|
+
incomingTaxonomyState: applyModel.planning.incomingTaxonomyState,
|
|
253
|
+
incomingTaxonomiesUpdatedAt: applyModel.planning.incomingTaxonomiesUpdatedAt,
|
|
422
254
|
workspaceTaxonomySyncMeta,
|
|
423
255
|
recordSyncDiagnostic,
|
|
424
256
|
});
|
|
425
257
|
applyAiProfileAndMemberSyncChanges({
|
|
426
258
|
core,
|
|
427
259
|
workspaceId,
|
|
428
|
-
incomingAiProfiles,
|
|
260
|
+
incomingAiProfiles: applyModel.profiles.incomingAiProfiles,
|
|
429
261
|
workspaceMembers: options?.workspaceMembers,
|
|
430
262
|
recordSyncDiagnostic,
|
|
431
263
|
assertSyncedAiProfilesPersisted,
|
|
432
264
|
createSyncRouteError,
|
|
433
265
|
});
|
|
434
|
-
|
|
266
|
+
const preparedTaskSnapshot = prepareTaskSyncSnapshotApply({
|
|
267
|
+
core,
|
|
268
|
+
workspaceId,
|
|
269
|
+
taskMutations: applyModel.taskMutations,
|
|
270
|
+
recordSyncDiagnostic,
|
|
271
|
+
});
|
|
272
|
+
validateTaskActorAssignments(workspaceId, preparedTaskSnapshot.tasks, preparedTaskSnapshot.archived);
|
|
435
273
|
await applyContentSyncChanges({
|
|
436
274
|
core,
|
|
437
275
|
workspaceId,
|
|
438
|
-
incomingDocuments,
|
|
439
|
-
incomingDocumentDeletes,
|
|
440
|
-
incomingAssets,
|
|
441
|
-
incomingAssetDeletes,
|
|
276
|
+
incomingDocuments: applyModel.content.incomingDocuments,
|
|
277
|
+
incomingDocumentDeletes: applyModel.content.incomingDocumentDeletes,
|
|
278
|
+
incomingAssets: applyModel.content.incomingAssets,
|
|
279
|
+
incomingAssetDeletes: applyModel.content.incomingAssetDeletes,
|
|
442
280
|
basePath: core.getPaths().basePath,
|
|
443
281
|
hasObjectStorageClient: () => Boolean(getObjectStorageClient()),
|
|
444
282
|
hasDocumentSyncMetadataDrift: contentSyncState.hasDocumentSyncMetadataDrift,
|
|
445
283
|
hasBinaryAssetSyncMetadataDrift: contentSyncState.hasBinaryAssetSyncMetadataDrift,
|
|
284
|
+
getExistingDocument: (docPath, targetWorkspaceId) => {
|
|
285
|
+
const asset = workspaceAssetStore?.getByStorageKey(docPath, targetWorkspaceId) || null;
|
|
286
|
+
return asset
|
|
287
|
+
? {
|
|
288
|
+
updatedAt: asset.updatedAt,
|
|
289
|
+
contentHash: asset.contentSha256
|
|
290
|
+
}
|
|
291
|
+
: null;
|
|
292
|
+
},
|
|
446
293
|
writeWorkspaceSyncDocument: contentSyncState.writeWorkspaceSyncDocument,
|
|
447
294
|
deleteWorkspaceSyncDocument: contentSyncState.deleteWorkspaceSyncDocument,
|
|
448
295
|
writeWorkspaceSyncBinaryAsset: contentSyncState.writeWorkspaceSyncBinaryAsset,
|
|
@@ -454,20 +301,34 @@ export function registerSyncRoutes(deps) {
|
|
|
454
301
|
applyCollaborationSyncChanges({
|
|
455
302
|
core,
|
|
456
303
|
workspaceId,
|
|
457
|
-
incomingDocumentReviewSessions,
|
|
458
|
-
incomingDocumentReviewSessionDeletes,
|
|
459
|
-
incomingDocumentReviewComments,
|
|
460
|
-
incomingDocumentReviewCommentDeletes,
|
|
461
|
-
incomingAnnotatedAttachmentSessions,
|
|
462
|
-
incomingAnnotatedAttachmentSessionDeletes,
|
|
304
|
+
incomingDocumentReviewSessions: applyModel.collaboration.incomingDocumentReviewSessions,
|
|
305
|
+
incomingDocumentReviewSessionDeletes: applyModel.collaboration.incomingDocumentReviewSessionDeletes,
|
|
306
|
+
incomingDocumentReviewComments: applyModel.collaboration.incomingDocumentReviewComments,
|
|
307
|
+
incomingDocumentReviewCommentDeletes: applyModel.collaboration.incomingDocumentReviewCommentDeletes,
|
|
308
|
+
incomingAnnotatedAttachmentSessions: applyModel.collaboration.incomingAnnotatedAttachmentSessions,
|
|
309
|
+
incomingAnnotatedAttachmentSessionDeletes: applyModel.collaboration.incomingAnnotatedAttachmentSessionDeletes,
|
|
310
|
+
recordSyncDiagnostic,
|
|
311
|
+
createSyncRouteError,
|
|
312
|
+
});
|
|
313
|
+
const deleteCount = applyTaskSyncDeletes({
|
|
314
|
+
core,
|
|
315
|
+
workspaceId,
|
|
316
|
+
taskMutations: applyModel.taskMutations,
|
|
317
|
+
recordSyncDiagnostic,
|
|
318
|
+
});
|
|
319
|
+
const result = applyPreparedTaskSyncSnapshot({
|
|
320
|
+
core,
|
|
321
|
+
workspaceId,
|
|
322
|
+
prepared: preparedTaskSnapshot,
|
|
323
|
+
});
|
|
324
|
+
applyTaskActivitySyncChanges({
|
|
325
|
+
core,
|
|
326
|
+
workspaceId,
|
|
327
|
+
incomingTaskEvents: applyModel.taskActivity.incomingTaskEvents,
|
|
463
328
|
recordSyncDiagnostic,
|
|
464
329
|
createSyncRouteError,
|
|
465
330
|
});
|
|
466
|
-
|
|
467
|
-
tasks: tasks.map(taskSyncPayloadToCoreTask),
|
|
468
|
-
archived: archived.map(taskSyncPayloadToCoreTask)
|
|
469
|
-
}, workspaceId);
|
|
470
|
-
return { result, deleteCount: incomingTaskDeletes.size, emittedEventIds: Array.from(emittedEventIds) };
|
|
331
|
+
return { result, deleteCount, emittedEventIds: Array.from(emittedEventIds) };
|
|
471
332
|
}
|
|
472
333
|
finally {
|
|
473
334
|
unsubscribeRealtime();
|
|
@@ -1257,6 +1118,10 @@ export function registerSyncRoutes(deps) {
|
|
|
1257
1118
|
}
|
|
1258
1119
|
}
|
|
1259
1120
|
let incomingChanges = Array.isArray(body?.changes) ? body.changes : [];
|
|
1121
|
+
const incomingTaskUpsertIds = incomingChanges
|
|
1122
|
+
.filter((change) => change?.op === 'upsert')
|
|
1123
|
+
.map((change) => String(change.task?.id || '').trim())
|
|
1124
|
+
.filter((taskId) => taskId.length > 0);
|
|
1260
1125
|
const allowReferenceNumberReassignment = body?.repair === true;
|
|
1261
1126
|
// Decrypt asset and document content if TASKFORCE_SYNC_KEY is configured on this server.
|
|
1262
1127
|
// The local runtime encrypts content before pushing; the cloud must decrypt before writing to storage.
|
|
@@ -1311,12 +1176,20 @@ export function registerSyncRoutes(deps) {
|
|
|
1311
1176
|
const { result, deleteCount, emittedEventIds } = await dispatchWorkspaceSyncChanges(targetWorkspaceId, incomingChanges, {
|
|
1312
1177
|
allowReferenceNumberReassignment
|
|
1313
1178
|
});
|
|
1179
|
+
const appliedTaskUpserts = incomingTaskUpsertIds
|
|
1180
|
+
.map((taskId) => core.getTask(taskId, targetWorkspaceId))
|
|
1181
|
+
.filter((task) => Boolean(task))
|
|
1182
|
+
.map((task) => ({
|
|
1183
|
+
archived: Boolean(task.isArchived),
|
|
1184
|
+
task: serializeTaskForSync(task)
|
|
1185
|
+
}));
|
|
1314
1186
|
const durationMs = Date.now() - startedAtMs;
|
|
1315
1187
|
const payload = JSON.stringify({
|
|
1316
1188
|
success: true,
|
|
1317
1189
|
workspaceId: targetWorkspaceId,
|
|
1318
1190
|
idempotencyKey: idempotencyKey || null,
|
|
1319
1191
|
emittedEventIds,
|
|
1192
|
+
appliedTaskUpserts,
|
|
1320
1193
|
result,
|
|
1321
1194
|
durationMs
|
|
1322
1195
|
});
|