@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
|
@@ -16,21 +16,34 @@ function insertFreePriceMapping(core, planVersionId) {
|
|
|
16
16
|
`).run(core.getTenantId(), planVersionId, 'month', `free:${planVersionId}:month`, 'free', 1, 0, null, now, now);
|
|
17
17
|
}
|
|
18
18
|
function seedPlanCatalog(core) {
|
|
19
|
-
core.upsertPlanCatalog({ planId: '
|
|
20
|
-
core.upsertPlanCatalog({ planId: '
|
|
19
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
20
|
+
core.upsertPlanCatalog({ planId: 'basic', displayName: 'Basic', status: 'active' });
|
|
21
|
+
core.upsertPlanCatalog({ planId: 'team-5', displayName: 'Team 5', status: 'active' });
|
|
21
22
|
core.updatePlanVersion({
|
|
22
|
-
planId: '
|
|
23
|
+
planId: 'basic',
|
|
24
|
+
versionNumber: 1,
|
|
25
|
+
seatLimit: null
|
|
26
|
+
});
|
|
27
|
+
core.updatePlanVersion({
|
|
28
|
+
planId: 'team-5',
|
|
23
29
|
versionNumber: 1,
|
|
24
30
|
seatLimit: 5
|
|
25
31
|
});
|
|
26
32
|
core.updatePlanVersionFeatures({
|
|
27
|
-
planVersionId: '
|
|
33
|
+
planVersionId: 'free-v1',
|
|
34
|
+
features: [
|
|
35
|
+
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
core.updatePlanVersionFeatures({
|
|
39
|
+
planVersionId: 'basic-v1',
|
|
28
40
|
features: [
|
|
41
|
+
{ featureKey: 'context.uploads', access: 'enabled' },
|
|
29
42
|
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
30
43
|
]
|
|
31
44
|
});
|
|
32
45
|
core.updatePlanVersionFeatures({
|
|
33
|
-
planVersionId: '
|
|
46
|
+
planVersionId: 'team-5-v1',
|
|
34
47
|
features: [
|
|
35
48
|
{ featureKey: 'collaboration.invites', access: 'enabled' },
|
|
36
49
|
{ featureKey: 'collaboration.team_management', access: 'enabled' },
|
|
@@ -38,7 +51,7 @@ function seedPlanCatalog(core) {
|
|
|
38
51
|
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
39
52
|
]
|
|
40
53
|
});
|
|
41
|
-
insertFreePriceMapping(core, '
|
|
54
|
+
insertFreePriceMapping(core, 'free-v1');
|
|
42
55
|
}
|
|
43
56
|
function seedPlans(core) {
|
|
44
57
|
seedPlanCatalog(core);
|
|
@@ -46,7 +59,7 @@ function seedPlans(core) {
|
|
|
46
59
|
billing: {
|
|
47
60
|
onboardingPolicy: {
|
|
48
61
|
mode: 'auto_grant_free',
|
|
49
|
-
planVersionId: '
|
|
62
|
+
planVersionId: 'free-v1'
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
});
|
|
@@ -78,7 +91,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
78
91
|
LIMIT 1
|
|
79
92
|
`).get('default', created.userId);
|
|
80
93
|
expect(row?.user_id).toBe(created.userId);
|
|
81
|
-
expect(row?.tier).toBe('
|
|
94
|
+
expect(row?.tier).toBe('free');
|
|
82
95
|
expect(row?.entitlement_state).toBe('active');
|
|
83
96
|
}
|
|
84
97
|
finally {
|
|
@@ -105,7 +118,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
105
118
|
LIMIT 1
|
|
106
119
|
`).get('default', created.userId);
|
|
107
120
|
expect(row?.user_id).toBe(created.userId);
|
|
108
|
-
expect(row?.tier).toBe('
|
|
121
|
+
expect(row?.tier).toBe('free');
|
|
109
122
|
expect(row?.entitlement_state).toBe('active');
|
|
110
123
|
}
|
|
111
124
|
finally {
|
|
@@ -118,14 +131,14 @@ describe('TaskforceCore system admin model', () => {
|
|
|
118
131
|
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
119
132
|
try {
|
|
120
133
|
core.createPlanVersion({
|
|
121
|
-
planId: '
|
|
134
|
+
planId: 'team-5',
|
|
122
135
|
versionNumber: 2,
|
|
123
136
|
isDefault: true,
|
|
124
137
|
seatLimit: 10,
|
|
125
138
|
trialDays: 14
|
|
126
139
|
});
|
|
127
140
|
core.updatePlanVersionFeatures({
|
|
128
|
-
planVersionId: '
|
|
141
|
+
planVersionId: 'team-5-v2',
|
|
129
142
|
features: [
|
|
130
143
|
{ featureKey: 'collaboration.invites', access: 'enabled' },
|
|
131
144
|
{ featureKey: 'collaboration.team_management', access: 'enabled' },
|
|
@@ -134,7 +147,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
134
147
|
]
|
|
135
148
|
});
|
|
136
149
|
core.createPlanVersion({
|
|
137
|
-
planId: '
|
|
150
|
+
planId: 'free',
|
|
138
151
|
versionNumber: 2,
|
|
139
152
|
isDefault: false,
|
|
140
153
|
seatLimit: null
|
|
@@ -143,7 +156,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
143
156
|
billing: {
|
|
144
157
|
onboardingPolicy: {
|
|
145
158
|
mode: 'auto_start_trial',
|
|
146
|
-
planVersionId: '
|
|
159
|
+
planVersionId: 'team-5-v2'
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
});
|
|
@@ -159,10 +172,10 @@ describe('TaskforceCore system admin model', () => {
|
|
|
159
172
|
WHERE tenant_id = ? AND user_id = ?
|
|
160
173
|
LIMIT 1
|
|
161
174
|
`).get(core.getTenantId(), created.userId);
|
|
162
|
-
expect(entitlement.planVersionId).toBe('
|
|
163
|
-
expect(entitlement.planId).toBe('
|
|
175
|
+
expect(entitlement.planVersionId).toBe('team-5-v2');
|
|
176
|
+
expect(entitlement.planId).toBe('team-5');
|
|
164
177
|
expect(entitlement.state).toBe('trialing');
|
|
165
|
-
expect(billing?.tier).toBe('
|
|
178
|
+
expect(billing?.tier).toBe('team-5');
|
|
166
179
|
expect(billing?.entitlement_state).toBe('trialing');
|
|
167
180
|
}
|
|
168
181
|
finally {
|
|
@@ -244,7 +257,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
244
257
|
access = excluded.access,
|
|
245
258
|
config_json = excluded.config_json,
|
|
246
259
|
updated_at = excluded.updated_at
|
|
247
|
-
`).run('default', '
|
|
260
|
+
`).run('default', 'team-5-v1', 'documents.workspace_access', 'enabled', '{}', now, now);
|
|
248
261
|
}
|
|
249
262
|
finally {
|
|
250
263
|
core.close();
|
|
@@ -257,9 +270,9 @@ describe('TaskforceCore system admin model', () => {
|
|
|
257
270
|
FROM plan_feature_matrix
|
|
258
271
|
WHERE tenant_id = ? AND plan_version_id = ? AND feature_key = ?
|
|
259
272
|
LIMIT 1
|
|
260
|
-
`).get('default', '
|
|
273
|
+
`).get('default', 'team-5-v1', 'documents.workspace_access');
|
|
261
274
|
expect(row).toBeUndefined();
|
|
262
|
-
const snapshot = core.listPlanVersions('
|
|
275
|
+
const snapshot = core.listPlanVersions('team-5').find((version) => version.planVersionId === 'team-5-v1');
|
|
263
276
|
expect(snapshot).toBeDefined();
|
|
264
277
|
expect(snapshot.features['documents.workspace_access']).toBeUndefined();
|
|
265
278
|
}
|
|
@@ -268,34 +281,120 @@ describe('TaskforceCore system admin model', () => {
|
|
|
268
281
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
269
282
|
}
|
|
270
283
|
});
|
|
271
|
-
it('
|
|
284
|
+
it('keeps configured auto-grant onboarding plan when registration includes a selected plan version', () => {
|
|
272
285
|
const projectRoot = makeProjectRoot();
|
|
273
286
|
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
274
287
|
try {
|
|
275
|
-
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
276
288
|
core.updatePlanVersion({
|
|
277
|
-
planId: '
|
|
289
|
+
planId: 'basic',
|
|
278
290
|
versionNumber: 1,
|
|
279
|
-
seatLimit:
|
|
291
|
+
seatLimit: null
|
|
280
292
|
});
|
|
281
|
-
insertFreePriceMapping(core, 'free-v1');
|
|
282
|
-
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'archived' });
|
|
283
293
|
core.updateGlobalSettings({
|
|
284
294
|
billing: {
|
|
285
295
|
onboardingPolicy: {
|
|
286
296
|
mode: 'auto_grant_free',
|
|
287
|
-
planVersionId: '
|
|
297
|
+
planVersionId: 'free-v1'
|
|
288
298
|
}
|
|
289
299
|
}
|
|
290
300
|
});
|
|
291
301
|
const created = core.registerPasswordUser({
|
|
292
302
|
email: 'selected-plan@example.com',
|
|
293
303
|
password: 'password123',
|
|
294
|
-
planVersionId: '
|
|
304
|
+
planVersionId: 'basic-v1'
|
|
305
|
+
});
|
|
306
|
+
expect(created.planSelectionRequired).toBe(false);
|
|
307
|
+
expect(core.getAccountEntitlement(created.userId)).toMatchObject({
|
|
308
|
+
planVersionId: 'free-v1',
|
|
309
|
+
planId: 'free',
|
|
310
|
+
state: 'active'
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
core.close();
|
|
315
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
it('uses the explicitly selected plan version when onboarding requires plan selection', () => {
|
|
319
|
+
const projectRoot = makeProjectRoot();
|
|
320
|
+
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
321
|
+
try {
|
|
322
|
+
core.updatePlanVersion({
|
|
323
|
+
planId: 'basic',
|
|
324
|
+
versionNumber: 1,
|
|
325
|
+
seatLimit: null
|
|
326
|
+
});
|
|
327
|
+
core.updateGlobalSettings({
|
|
328
|
+
billing: {
|
|
329
|
+
onboardingPolicy: {
|
|
330
|
+
mode: 'require_plan_selection',
|
|
331
|
+
planVersionId: null
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
const created = core.registerPasswordUser({
|
|
336
|
+
email: 'selected-plan-required@example.com',
|
|
337
|
+
password: 'password123',
|
|
338
|
+
planVersionId: 'basic-v1'
|
|
295
339
|
});
|
|
296
340
|
expect(created.planSelectionRequired).toBe(false);
|
|
297
341
|
expect(core.getAccountEntitlement(created.userId)).toMatchObject({
|
|
342
|
+
planVersionId: 'basic-v1',
|
|
343
|
+
planId: 'basic',
|
|
344
|
+
state: 'checkout_pending'
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
core.close();
|
|
349
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
it('auto-heals stranded legacy personal free entitlements onto the active default free version', () => {
|
|
353
|
+
const projectRoot = makeProjectRoot();
|
|
354
|
+
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
355
|
+
try {
|
|
356
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
357
|
+
insertFreePriceMapping(core, 'free-v1');
|
|
358
|
+
core.createPlanVersion({
|
|
359
|
+
planId: 'free',
|
|
360
|
+
versionNumber: 2,
|
|
361
|
+
isDefault: true,
|
|
362
|
+
seatLimit: 1
|
|
363
|
+
});
|
|
364
|
+
insertFreePriceMapping(core, 'free-v2');
|
|
365
|
+
core.updateGlobalSettings({
|
|
366
|
+
billing: {
|
|
367
|
+
onboardingPolicy: {
|
|
368
|
+
mode: 'require_plan_selection',
|
|
369
|
+
planVersionId: null
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
const created = core.registerPasswordUser({
|
|
374
|
+
email: 'legacy-free@example.com',
|
|
375
|
+
password: 'password123',
|
|
376
|
+
planVersionId: 'free-v1'
|
|
377
|
+
});
|
|
378
|
+
core.updateAccountEntitlement({
|
|
379
|
+
accountId: created.userId,
|
|
298
380
|
planVersionId: 'free-v1',
|
|
381
|
+
entitlementState: 'canceled',
|
|
382
|
+
actorUserId: created.userId,
|
|
383
|
+
actorRole: 'user',
|
|
384
|
+
reason: 'legacy-free-v1-canceled'
|
|
385
|
+
});
|
|
386
|
+
expect(core.getAccountAccessStatus(created.userId)).toMatchObject({
|
|
387
|
+
gate: 'ok',
|
|
388
|
+
hasEntitlement: true,
|
|
389
|
+
canAccessApp: true,
|
|
390
|
+
entitlement: {
|
|
391
|
+
planVersionId: 'free-v2',
|
|
392
|
+
planId: 'free',
|
|
393
|
+
state: 'active'
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
expect(core.getAccountEntitlement(created.userId)).toMatchObject({
|
|
397
|
+
planVersionId: 'free-v2',
|
|
299
398
|
planId: 'free',
|
|
300
399
|
state: 'active'
|
|
301
400
|
});
|
|
@@ -305,6 +404,61 @@ describe('TaskforceCore system admin model', () => {
|
|
|
305
404
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
306
405
|
}
|
|
307
406
|
});
|
|
407
|
+
it('does not auto-heal canceled accounts already on the current default free version', () => {
|
|
408
|
+
const projectRoot = makeProjectRoot();
|
|
409
|
+
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
410
|
+
try {
|
|
411
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
412
|
+
insertFreePriceMapping(core, 'free-v1');
|
|
413
|
+
core.createPlanVersion({
|
|
414
|
+
planId: 'free',
|
|
415
|
+
versionNumber: 2,
|
|
416
|
+
isDefault: true,
|
|
417
|
+
seatLimit: 1
|
|
418
|
+
});
|
|
419
|
+
insertFreePriceMapping(core, 'free-v2');
|
|
420
|
+
core.updateGlobalSettings({
|
|
421
|
+
billing: {
|
|
422
|
+
onboardingPolicy: {
|
|
423
|
+
mode: 'require_plan_selection',
|
|
424
|
+
planVersionId: null
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
const created = core.registerPasswordUser({
|
|
429
|
+
email: 'current-default-free@example.com',
|
|
430
|
+
password: 'password123',
|
|
431
|
+
planVersionId: 'free-v2'
|
|
432
|
+
});
|
|
433
|
+
core.updateAccountEntitlement({
|
|
434
|
+
accountId: created.userId,
|
|
435
|
+
planVersionId: 'free-v2',
|
|
436
|
+
entitlementState: 'canceled',
|
|
437
|
+
actorUserId: created.userId,
|
|
438
|
+
actorRole: 'user',
|
|
439
|
+
reason: 'intentional-free-v2-cancel'
|
|
440
|
+
});
|
|
441
|
+
expect(core.getAccountAccessStatus(created.userId)).toMatchObject({
|
|
442
|
+
gate: 'missing_entitlement',
|
|
443
|
+
hasEntitlement: true,
|
|
444
|
+
canAccessApp: false,
|
|
445
|
+
entitlement: {
|
|
446
|
+
planVersionId: 'free-v2',
|
|
447
|
+
planId: 'free',
|
|
448
|
+
state: 'canceled'
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
expect(core.getAccountEntitlement(created.userId)).toMatchObject({
|
|
452
|
+
planVersionId: 'free-v2',
|
|
453
|
+
planId: 'free',
|
|
454
|
+
state: 'canceled'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
core.close();
|
|
459
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
460
|
+
}
|
|
461
|
+
});
|
|
308
462
|
it('treats accounts without an entitlement or pending-selection marker as missing entitlement', () => {
|
|
309
463
|
const projectRoot = makeProjectRoot();
|
|
310
464
|
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
@@ -344,7 +498,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
344
498
|
UPDATE plan_versions
|
|
345
499
|
SET is_default = 0
|
|
346
500
|
WHERE tenant_id = ? AND plan_id = ?
|
|
347
|
-
`).run('default', '
|
|
501
|
+
`).run('default', 'free');
|
|
348
502
|
}
|
|
349
503
|
finally {
|
|
350
504
|
initial.close();
|
|
@@ -357,10 +511,10 @@ describe('TaskforceCore system admin model', () => {
|
|
|
357
511
|
FROM plan_versions
|
|
358
512
|
WHERE tenant_id = ? AND plan_id = ? AND plan_version_id = ?
|
|
359
513
|
LIMIT 1
|
|
360
|
-
`).get('default', '
|
|
361
|
-
expect(row?.plan_version_id).toBe('
|
|
514
|
+
`).get('default', 'free', 'free-v1');
|
|
515
|
+
expect(row?.plan_version_id).toBe('free-v1');
|
|
362
516
|
expect(row?.is_default).toBe(1);
|
|
363
|
-
expect(reopened.getRequiredDefaultPlanVersionId('
|
|
517
|
+
expect(reopened.getRequiredDefaultPlanVersionId('free')).toBe('free-v1');
|
|
364
518
|
}
|
|
365
519
|
finally {
|
|
366
520
|
reopened.close();
|
|
@@ -405,7 +559,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
405
559
|
UPDATE plan_versions
|
|
406
560
|
SET seat_limit = NULL, updated_at = ?
|
|
407
561
|
WHERE tenant_id = ? AND plan_version_id = ?
|
|
408
|
-
`).run(now, 'default', '
|
|
562
|
+
`).run(now, 'default', 'team-5-v1');
|
|
409
563
|
db.prepare(`
|
|
410
564
|
INSERT INTO plan_feature_matrix (tenant_id, plan_version_id, feature_key, access, config_json, created_at, updated_at)
|
|
411
565
|
VALUES (?, ?, 'collaboration.seat_limit', 'limited', ?, ?, ?)
|
|
@@ -413,14 +567,14 @@ describe('TaskforceCore system admin model', () => {
|
|
|
413
567
|
access = excluded.access,
|
|
414
568
|
config_json = excluded.config_json,
|
|
415
569
|
updated_at = excluded.updated_at
|
|
416
|
-
`).run('default', '
|
|
570
|
+
`).run('default', 'team-5-v1', JSON.stringify({ seat_limit: 7 }), now, now);
|
|
417
571
|
}
|
|
418
572
|
finally {
|
|
419
573
|
initial.close();
|
|
420
574
|
}
|
|
421
575
|
const reopened = openCore(projectRoot, { seedDefaultPlans: false });
|
|
422
576
|
try {
|
|
423
|
-
const version = reopened.getPlanVersionFeatures('
|
|
577
|
+
const version = reopened.getPlanVersionFeatures('team-5', 'team-5-v1');
|
|
424
578
|
expect(version.seatLimit).toBe(7);
|
|
425
579
|
expect(version.features['collaboration.seat_limit']?.config || {}).toEqual({});
|
|
426
580
|
}
|
|
@@ -453,13 +607,15 @@ describe('TaskforceCore system admin model', () => {
|
|
|
453
607
|
const reopened = openCore(projectRoot, { seedDefaultPlans: false });
|
|
454
608
|
try {
|
|
455
609
|
const db = reopened.db;
|
|
610
|
+
const normalizedPersonalVersionId = reopened.getDefaultPlanVersionIdByMode('personal');
|
|
611
|
+
const normalizedBillingMirror = reopened.getBillingMirrorProfileForPlanVersion(normalizedPersonalVersionId);
|
|
456
612
|
const row = db.prepare(`
|
|
457
613
|
SELECT tier, entitlement_state
|
|
458
614
|
FROM user_billing
|
|
459
615
|
WHERE tenant_id = ? AND user_id = ?
|
|
460
616
|
LIMIT 1
|
|
461
617
|
`).get('default', legacyUserId);
|
|
462
|
-
expect(row?.tier).toBe(
|
|
618
|
+
expect(row?.tier).toBe(normalizedBillingMirror.planId);
|
|
463
619
|
expect(row?.entitlement_state).toBe('active');
|
|
464
620
|
}
|
|
465
621
|
finally {
|
|
@@ -490,11 +646,11 @@ describe('TaskforceCore system admin model', () => {
|
|
|
490
646
|
db.prepare(`
|
|
491
647
|
INSERT INTO plan_catalog (plan_id, tenant_id, display_name, status, created_at, updated_at)
|
|
492
648
|
VALUES (?, ?, ?, 'active', ?, ?)
|
|
493
|
-
`).run('
|
|
649
|
+
`).run('agency', 'default', 'Agency', now, now);
|
|
494
650
|
db.prepare(`
|
|
495
651
|
INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
|
|
496
652
|
VALUES (?, ?, ?, 1, 1, 25, '{}', ?, ?)
|
|
497
|
-
`).run('
|
|
653
|
+
`).run('agency-v1', 'default', 'agency', now, now);
|
|
498
654
|
}
|
|
499
655
|
finally {
|
|
500
656
|
initial.close();
|
|
@@ -502,8 +658,8 @@ describe('TaskforceCore system admin model', () => {
|
|
|
502
658
|
const reopened = openCore(projectRoot, { seedDefaultPlans: false });
|
|
503
659
|
try {
|
|
504
660
|
const plans = reopened.listPlanCatalog();
|
|
505
|
-
expect(plans.map((plan) => plan.planId)).toEqual(['
|
|
506
|
-
expect(reopened.getDefaultPlanVersionIdByMode('personal')).toBe('
|
|
661
|
+
expect(plans.map((plan) => plan.planId)).toEqual(['agency']);
|
|
662
|
+
expect(reopened.getDefaultPlanVersionIdByMode('personal')).toBe('agency-v1');
|
|
507
663
|
}
|
|
508
664
|
finally {
|
|
509
665
|
reopened.close();
|
|
@@ -613,13 +769,13 @@ describe('TaskforceCore system admin model', () => {
|
|
|
613
769
|
password: 'password123'
|
|
614
770
|
});
|
|
615
771
|
core.createPlanVersion({
|
|
616
|
-
planId: '
|
|
772
|
+
planId: 'free',
|
|
617
773
|
versionNumber: 2,
|
|
618
774
|
isDefault: true
|
|
619
775
|
});
|
|
620
776
|
core.updateAccountEntitlement({
|
|
621
777
|
accountId: member.userId,
|
|
622
|
-
planVersionId: '
|
|
778
|
+
planVersionId: 'free-v2',
|
|
623
779
|
entitlementState: 'active',
|
|
624
780
|
actorUserId: admin.userId,
|
|
625
781
|
actorRole: 'system_admin'
|
|
@@ -741,7 +897,7 @@ describe('TaskforceCore system admin model', () => {
|
|
|
741
897
|
ON CONFLICT (user_id) DO UPDATE SET
|
|
742
898
|
stripe_customer_id = excluded.stripe_customer_id,
|
|
743
899
|
updated_at = excluded.updated_at
|
|
744
|
-
`).run(member.userId, 'default', 'cus_test_123', 'active', '
|
|
900
|
+
`).run(member.userId, 'default', 'cus_test_123', 'active', 'basic', now, now);
|
|
745
901
|
expect(() => core.purgeSystemUser({
|
|
746
902
|
targetUserId: member.userId,
|
|
747
903
|
actorUserId: admin.userId,
|
|
@@ -758,16 +914,16 @@ describe('TaskforceCore system admin model', () => {
|
|
|
758
914
|
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
759
915
|
try {
|
|
760
916
|
const plan = core.upsertPlanCatalog({
|
|
761
|
-
planId: '
|
|
762
|
-
displayName: '
|
|
917
|
+
planId: 'agency',
|
|
918
|
+
displayName: 'Agency',
|
|
763
919
|
status: 'active'
|
|
764
920
|
});
|
|
765
|
-
expect(plan.planId).toBe('
|
|
766
|
-
const versions = core.listPlanVersions('
|
|
921
|
+
expect(plan.planId).toBe('agency');
|
|
922
|
+
const versions = core.listPlanVersions('agency');
|
|
767
923
|
expect(versions).toHaveLength(1);
|
|
768
924
|
expect(versions[0]).toMatchObject({
|
|
769
|
-
planVersionId: '
|
|
770
|
-
planId: '
|
|
925
|
+
planVersionId: 'agency-v1',
|
|
926
|
+
planId: 'agency',
|
|
771
927
|
versionNumber: 1,
|
|
772
928
|
isDefault: true,
|
|
773
929
|
seatLimit: null
|
|
@@ -783,24 +939,24 @@ describe('TaskforceCore system admin model', () => {
|
|
|
783
939
|
const core = openCore(projectRoot, { seedDefaultPlans: true });
|
|
784
940
|
try {
|
|
785
941
|
core.upsertPlanCatalog({
|
|
786
|
-
planId: '
|
|
787
|
-
displayName: '
|
|
942
|
+
planId: 'agency',
|
|
943
|
+
displayName: 'Agency',
|
|
788
944
|
status: 'active'
|
|
789
945
|
});
|
|
790
946
|
core.createPlanVersion({
|
|
791
|
-
planId: '
|
|
947
|
+
planId: 'agency',
|
|
792
948
|
versionNumber: 2,
|
|
793
949
|
isDefault: true,
|
|
794
950
|
seatLimit: 25
|
|
795
951
|
});
|
|
796
952
|
core.upsertPlanCatalog({
|
|
797
|
-
planId: '
|
|
798
|
-
displayName: '
|
|
953
|
+
planId: 'agency',
|
|
954
|
+
displayName: 'Agency Plus',
|
|
799
955
|
status: 'archived'
|
|
800
956
|
});
|
|
801
|
-
const versions = core.listPlanVersions('
|
|
802
|
-
expect(versions.map((version) => version.planVersionId)).toEqual(['
|
|
803
|
-
expect(versions.find((version) => version.planVersionId === '
|
|
957
|
+
const versions = core.listPlanVersions('agency');
|
|
958
|
+
expect(versions.map((version) => version.planVersionId)).toEqual(['agency-v2', 'agency-v1']);
|
|
959
|
+
expect(versions.find((version) => version.planVersionId === 'agency-v1')).toMatchObject({
|
|
804
960
|
isDefault: false
|
|
805
961
|
});
|
|
806
962
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TaskActor } from '../shared/taskActor.js';
|
|
3
|
+
import type { Comment, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
|
|
3
4
|
export interface TaskActivityServiceDeps {
|
|
4
5
|
db: DatabaseAdapter;
|
|
5
6
|
tenantId: string;
|
|
@@ -7,6 +8,7 @@ export interface TaskActivityServiceDeps {
|
|
|
7
8
|
normalizeWorkspaceId: (workspaceId: string | undefined) => string;
|
|
8
9
|
resolveWorkspaceActor: (workspaceId: string, actorRef: unknown) => TaskActor | null;
|
|
9
10
|
}
|
|
11
|
+
export declare function listWorkspaceTaskEventsService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
|
|
10
12
|
export declare function replaceTaskCommentsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, comments: Comment[]): void;
|
|
11
13
|
export declare function listTaskEventsInternalService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string): TaskEvent[];
|
|
12
14
|
export declare function buildTaskActivityService(comments: Comment[], taskEvents: TaskEvent[]): TaskActivityItem[];
|
|
@@ -19,3 +21,4 @@ export declare function addTaskEventService(deps: TaskActivityServiceDeps, input
|
|
|
19
21
|
details?: TaskEventDetails;
|
|
20
22
|
}): void;
|
|
21
23
|
export declare function replaceTaskEventsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, taskEvents: TaskEvent[]): void;
|
|
24
|
+
export declare function upsertTaskEventForSyncService(deps: TaskActivityServiceDeps, event: TaskEvent): TaskEvent;
|
|
@@ -39,6 +39,17 @@ function mapTaskEventRow(deps, row) {
|
|
|
39
39
|
actorProfile: actor === 'system' ? null : deps.resolveWorkspaceActor(workspaceId, actor)
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
+
export function listWorkspaceTaskEventsService(deps, workspaceId) {
|
|
43
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
44
|
+
const rows = deps.db.prepare(`
|
|
45
|
+
SELECT id, task_id, workspace_id, action, actor, actor_type, details_json, created_at
|
|
46
|
+
FROM task_events
|
|
47
|
+
WHERE tenant_id = ?
|
|
48
|
+
AND workspace_id = ?
|
|
49
|
+
ORDER BY created_at ASC, id ASC
|
|
50
|
+
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
51
|
+
return rows.map((row) => mapTaskEventRow(deps, row));
|
|
52
|
+
}
|
|
42
53
|
export function replaceTaskCommentsService(deps, taskId, workspaceId, comments) {
|
|
43
54
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
44
55
|
deps.db.prepare('DELETE FROM comments WHERE tenant_id = ? AND workspace_id = ? AND task_id = ?')
|
|
@@ -128,3 +139,37 @@ export function replaceTaskEventsService(deps, taskId, workspaceId, taskEvents)
|
|
|
128
139
|
insertTaskEvent.run(event.id, deps.tenantId, normalizedWorkspaceId, taskId, event.action, event.actor, event.actorType, JSON.stringify(normalizeTaskEventDetails(event.details)), event.createdAt);
|
|
129
140
|
}
|
|
130
141
|
}
|
|
142
|
+
export function upsertTaskEventForSyncService(deps, event) {
|
|
143
|
+
const workspaceId = deps.normalizeWorkspaceId(event.workspaceId);
|
|
144
|
+
const normalizedEvent = {
|
|
145
|
+
id: String(event.id || '').trim(),
|
|
146
|
+
taskId: String(event.taskId || '').trim(),
|
|
147
|
+
workspaceId,
|
|
148
|
+
action: String(event.action || '').trim(),
|
|
149
|
+
actor: String(event.actor || 'system').trim() || 'system',
|
|
150
|
+
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
151
|
+
? event.actorType
|
|
152
|
+
: 'system',
|
|
153
|
+
createdAt: String(event.createdAt || '').trim() || new Date().toISOString(),
|
|
154
|
+
details: normalizeTaskEventDetails(event.details)
|
|
155
|
+
};
|
|
156
|
+
deps.db.prepare(`
|
|
157
|
+
INSERT INTO task_events (
|
|
158
|
+
id, tenant_id, workspace_id, task_id, action, actor, actor_type, details_json, created_at
|
|
159
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
160
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
161
|
+
workspace_id = excluded.workspace_id,
|
|
162
|
+
task_id = excluded.task_id,
|
|
163
|
+
action = excluded.action,
|
|
164
|
+
actor = excluded.actor,
|
|
165
|
+
actor_type = excluded.actor_type,
|
|
166
|
+
details_json = excluded.details_json,
|
|
167
|
+
created_at = excluded.created_at
|
|
168
|
+
`).run(normalizedEvent.id, deps.tenantId, workspaceId, normalizedEvent.taskId, normalizedEvent.action, normalizedEvent.actor, normalizedEvent.actorType, JSON.stringify(normalizedEvent.details), normalizedEvent.createdAt);
|
|
169
|
+
return {
|
|
170
|
+
...normalizedEvent,
|
|
171
|
+
actorProfile: normalizedEvent.actor === 'system'
|
|
172
|
+
? null
|
|
173
|
+
: deps.resolveWorkspaceActor(workspaceId, normalizedEvent.actor)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -92,7 +92,7 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
92
92
|
cleanup();
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
|
-
it('suppresses task events
|
|
95
|
+
it('suppresses task events when skipTaskEvents is explicitly true', () => {
|
|
96
96
|
const { core, cleanup } = makeCore();
|
|
97
97
|
try {
|
|
98
98
|
const task = core.createTask({ title: 'Autosave task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
@@ -110,6 +110,57 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
110
110
|
cleanup();
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
|
+
it('suppresses title and description autosave events while preserving the saved task content', () => {
|
|
114
|
+
const { core, cleanup } = makeCore();
|
|
115
|
+
try {
|
|
116
|
+
const task = core.createTask({ title: 'Autosave title task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
117
|
+
core.updateTask(task.id, {
|
|
118
|
+
title: 'Autosave title task updated',
|
|
119
|
+
description: 'Draft body'
|
|
120
|
+
}, 'workspace-a', {
|
|
121
|
+
actorRef: 'user',
|
|
122
|
+
saveSource: 'autosave'
|
|
123
|
+
});
|
|
124
|
+
const events = core.listTaskEvents(task.id, 'workspace-a');
|
|
125
|
+
const hydrated = core.getTask(task.id, 'workspace-a');
|
|
126
|
+
expect(events.map((event) => event.action)).toEqual(['task-created']);
|
|
127
|
+
expect(hydrated?.title).toBe('Autosave title task updated');
|
|
128
|
+
expect(hydrated?.description).toBe('Draft body');
|
|
129
|
+
expect(hydrated?.activity?.some((item) => item.type === 'event' && item.event.action === 'task-updated')).toBe(false);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
cleanup();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
it('keeps meaningful autosave updates in the activity feed while filtering noisy text changes', () => {
|
|
136
|
+
const { core, cleanup } = makeCore();
|
|
137
|
+
try {
|
|
138
|
+
const task = core.createTask({ title: 'Autosave assignee task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
139
|
+
core.updateTask(task.id, {
|
|
140
|
+
title: 'Autosave assignee task updated',
|
|
141
|
+
assignee: 'member-1'
|
|
142
|
+
}, 'workspace-a', {
|
|
143
|
+
actorRef: 'user',
|
|
144
|
+
saveSource: 'autosave'
|
|
145
|
+
});
|
|
146
|
+
const assignmentEvent = core.listTaskEvents(task.id, 'workspace-a').find((event) => event.action === 'task-updated' && event.details?.changes?.assignee);
|
|
147
|
+
expect(assignmentEvent).toEqual(expect.objectContaining({
|
|
148
|
+
action: 'task-updated',
|
|
149
|
+
details: {
|
|
150
|
+
changes: {
|
|
151
|
+
assignee: {
|
|
152
|
+
from: 'unassigned',
|
|
153
|
+
to: 'member-1',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
expect(assignmentEvent?.details?.changes?.title).toBeUndefined();
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
cleanup();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
113
164
|
it('captures assignment changes in the core task update event details', () => {
|
|
114
165
|
const { core, cleanup } = makeCore();
|
|
115
166
|
try {
|