@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.
Files changed (165) hide show
  1. package/dist/Taskforce.module.css +102 -5
  2. package/dist/TaskforceCore.js +514 -212
  3. package/dist/TaskforceCore.test.js +1172 -133
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  21. package/dist/components/task/TaskStatusActions.js +8 -3
  22. package/dist/components/task/TaskStatusActions.test.js +9 -0
  23. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  24. package/dist/components/task/taskKanbanDropRules.js +187 -0
  25. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  26. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  27. package/dist/components/ui/Modal.d.ts +1 -1
  28. package/dist/components/ui/Modal.js +1 -0
  29. package/dist/components/views/AppShellHeader.js +5 -1
  30. package/dist/components/views/AppShellHeader.test.js +4 -0
  31. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  32. package/dist/components/views/PlanComparisonPage.js +4 -2
  33. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  34. package/dist/components/views/PlansPage.d.ts +5 -1
  35. package/dist/components/views/PlansPage.js +39 -12
  36. package/dist/components/views/PlansPage.test.js +59 -2
  37. package/dist/components/views/StandaloneLayout.js +113 -38
  38. package/dist/components/views/WidgetView.js +11 -2
  39. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  40. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  41. package/dist/core/PlanEntitlementService.d.ts +3 -1
  42. package/dist/core/PlanEntitlementService.js +61 -11
  43. package/dist/core/SignupMonetizationSettings.test.js +37 -2
  44. package/dist/core/TaskActivityService.d.ts +4 -1
  45. package/dist/core/TaskActivityService.js +45 -0
  46. package/dist/core/TaskAuditTimeline.test.js +52 -1
  47. package/dist/core/Taskforce.d.ts +15 -11
  48. package/dist/core/Taskforce.js +43 -2
  49. package/dist/core/WorkspacePlanMode.test.js +65 -0
  50. package/dist/documentReviews/service.d.ts +1 -1
  51. package/dist/documentReviews/types.d.ts +1 -1
  52. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  53. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  54. package/dist/hooks/sync/auth.d.ts +37 -0
  55. package/dist/hooks/sync/auth.js +61 -0
  56. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  57. package/dist/hooks/sync/bootstrap.js +52 -0
  58. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  59. package/dist/hooks/sync/orchestratorShared.js +209 -0
  60. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  61. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  62. package/dist/hooks/sync/realtime.d.ts +25 -0
  63. package/dist/hooks/sync/realtime.js +60 -0
  64. package/dist/hooks/sync/recovery.d.ts +69 -0
  65. package/dist/hooks/sync/recovery.js +118 -0
  66. package/dist/hooks/sync/transfers.d.ts +122 -0
  67. package/dist/hooks/sync/transfers.js +431 -0
  68. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  69. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  70. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  71. package/dist/hooks/ui/useResourceExport.js +5 -2
  72. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  73. package/dist/hooks/useRealtimeSync.js +2 -1
  74. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  75. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  76. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  77. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  78. package/dist/hooks/useSyncOrchestrator.js +314 -873
  79. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
  80. package/dist/hooks/useTaskMutations.js +45 -6
  81. package/dist/hooks/useTaskMutations.test.js +147 -0
  82. package/dist/hooks/useTaskforce.d.ts +8 -2
  83. package/dist/hooks/useTaskforce.js +285 -812
  84. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  85. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  86. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  87. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  88. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  89. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  90. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  91. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  92. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  93. package/dist/localization/locales/en-US.js +2 -2
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  95. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  96. package/dist/mcp/clientIntegrations.d.ts +12 -1
  97. package/dist/mcp/clientIntegrations.js +161 -6
  98. package/dist/mcp/clientIntegrations.test.js +76 -4
  99. package/dist/mcp/documentHelpers.d.ts +100 -0
  100. package/dist/mcp/documentHelpers.js +161 -0
  101. package/dist/mcp/runtime.js +174 -476
  102. package/dist/mcp/runtime.test.js +81 -8
  103. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  104. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  105. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  106. package/dist/mcp/taskAttachmentTypes.js +1 -0
  107. package/dist/plugins/vite.js +1 -1
  108. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  109. package/dist/runtime/appGateDefinitions.js +1 -1
  110. package/dist/server/index.d.ts +1 -0
  111. package/dist/server/index.js +46 -3
  112. package/dist/server/index.test.js +12 -1
  113. package/dist/server/routes/admin.js +13 -19
  114. package/dist/server/routes/auth.js +37 -2
  115. package/dist/server/routes/billing.js +18 -67
  116. package/dist/server/routes/billing.test.js +43 -0
  117. package/dist/server/routes/shared.d.ts +30 -0
  118. package/dist/server/routes/shared.js +26 -0
  119. package/dist/server/routes/sync.integration.test.js +20 -0
  120. package/dist/server/routes/sync.js +110 -237
  121. package/dist/server/routes/tasks.js +49 -7
  122. package/dist/server/routes.js +8 -19
  123. package/dist/server/routes.test.js +243 -5
  124. package/dist/shared/taskActor.d.ts +10 -0
  125. package/dist/shared/taskActor.js +1 -0
  126. package/dist/sync/collaborationSyncPayload.js +4 -0
  127. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  128. package/dist/sync/collaborationSyncState.d.ts +3 -1
  129. package/dist/sync/collaborationSyncState.js +19 -0
  130. package/dist/sync/contentSyncState.js +12 -10
  131. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  132. package/dist/sync/syncApplyHandlers.js +370 -8
  133. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  134. package/dist/sync/syncApplyHandlers.test.js +377 -0
  135. package/dist/sync/syncService.d.ts +8 -1
  136. package/dist/sync/syncService.js +36 -2
  137. package/dist/sync/syncService.test.js +48 -0
  138. package/dist/sync/workspacePullFeed.d.ts +1 -0
  139. package/dist/sync/workspacePullFeed.js +66 -59
  140. package/dist/sync/workspacePullFeed.test.js +72 -4
  141. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  142. package/dist/sync/workspaceSyncModel.js +65 -0
  143. package/dist/sync/workspaceSyncModel.test.js +119 -0
  144. package/dist/types.d.ts +2 -10
  145. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  146. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  147. package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  148. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
  149. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  150. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  151. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  152. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
  153. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  154. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  155. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
  156. package/dist/ui/index.html +4 -4
  157. package/dist/utils/taskActivity.js +4 -0
  158. package/dist/utils/taskActivity.test.js +25 -1
  159. package/package.json +3 -1
  160. package/scripts/playwright-auth.sh +4 -0
  161. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  162. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  163. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  164. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  165. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -55,10 +55,18 @@ import { getRuntimeCapabilities } from '../../runtime/capabilities.js';
55
55
  import { resolveBuildInfo } from '../buildInfo.js';
56
56
  import { resolveRealtimeSyncFlag } from '../../sync/realtimeFeature.js';
57
57
  import { parseJsonBody } from '../routes.js';
58
+ import { resolveRequestAccess } from '../auth.js';
58
59
  import { resolveErrorStatusCode, requestHeader, requestRuntimeMode } from './shared.js';
59
60
  import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
60
61
  import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
61
62
  import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
63
+ function createUiStateRequestId() {
64
+ return `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
65
+ }
66
+ function attachUiStateDebugHeaders(res, requestId) {
67
+ res.setHeader('x-taskforce-ui-state-route', 'tasks');
68
+ res.setHeader('x-taskforce-ui-state-request-id', requestId);
69
+ }
62
70
  // === SECTION: Registration ===
63
71
  export function registerTaskRoutes(deps) {
64
72
  const { addRoute, core, context, requestWorkspaceId, ensureAppAccess, resolveEffectiveObjectStorageConfig, buildBatchSummary, CLOUD_SAFE_CONFIG_PATHS, } = deps;
@@ -155,16 +163,31 @@ export function registerTaskRoutes(deps) {
155
163
  || ((isLocalRuntime && allowCreate && !explicitWorkspaceId) ? '' : headerWorkspaceId)).trim() || 'default';
156
164
  try {
157
165
  const workspaceExists = Boolean(core.getWorkspaceProfile(incomingWorkspaceId));
166
+ const authConfig = context.authConfig;
167
+ const requestAccess = authConfig?.enabled
168
+ ? resolveRequestAccess(req, authConfig)
169
+ : null;
158
170
  const shouldCreateWorkspace = Boolean(name
159
171
  && (incomingWorkspaceId === 'default' || !workspaceExists)
160
172
  && (isLocalRuntime || allowCreate));
161
173
  const workspace = shouldCreateWorkspace
162
- ? core.createWorkspace({
163
- workspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
164
- name,
165
- description,
166
- slug
167
- })
174
+ ? (!isLocalRuntime
175
+ && requestAccess?.authenticated
176
+ && requestAccess.userId
177
+ && requestAccess.userId !== 'anonymous'
178
+ ? core.createWorkspaceForUser({
179
+ userId: requestAccess.userId,
180
+ preferredWorkspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
181
+ name,
182
+ description,
183
+ role: 'owner'
184
+ })
185
+ : core.createWorkspace({
186
+ workspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
187
+ name,
188
+ description,
189
+ slug
190
+ }))
168
191
  : core.updateWorkspaceProfile(incomingWorkspaceId, { name, description, slug });
169
192
  res.writeHead(200, { 'Content-Type': 'application/json' });
170
193
  res.end(JSON.stringify({ success: true, workspace }));
@@ -179,6 +202,9 @@ export function registerTaskRoutes(deps) {
179
202
  addRoute('GET', '/api/taskforce/ui-state', async (req, res) => {
180
203
  if (!ensureAppAccess(req, res))
181
204
  return;
205
+ const requestId = createUiStateRequestId();
206
+ const startedAt = Date.now();
207
+ attachUiStateDebugHeaders(res, requestId);
182
208
  try {
183
209
  const url = new URL(req.url || '/', 'http://localhost');
184
210
  const requestedWorkspaceId = String(url.searchParams.get('workspaceId') || '').trim();
@@ -194,6 +220,12 @@ export function registerTaskRoutes(deps) {
194
220
  }
195
221
  catch (error) {
196
222
  const statusCode = resolveErrorStatusCode(error, 400);
223
+ console.warn('[Taskforce][ui-state] GET failed', {
224
+ requestId,
225
+ durationMs: Date.now() - startedAt,
226
+ error: String(error?.message || 'Failed to read UI state'),
227
+ path: req.url || '/api/taskforce/ui-state'
228
+ });
197
229
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
198
230
  res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to read UI state') }));
199
231
  }
@@ -202,6 +234,9 @@ export function registerTaskRoutes(deps) {
202
234
  addRoute('POST', '/api/taskforce/ui-state', async (req, res) => {
203
235
  if (!ensureAppAccess(req, res))
204
236
  return;
237
+ const requestId = createUiStateRequestId();
238
+ const startedAt = Date.now();
239
+ attachUiStateDebugHeaders(res, requestId);
205
240
  const body = await parseJsonBody(req);
206
241
  try {
207
242
  const requestedWorkspaceId = String(body?.workspaceId || '').trim();
@@ -222,6 +257,13 @@ export function registerTaskRoutes(deps) {
222
257
  }
223
258
  catch (error) {
224
259
  const statusCode = resolveErrorStatusCode(error, 400);
260
+ console.warn('[Taskforce][ui-state] POST failed', {
261
+ requestId,
262
+ durationMs: Date.now() - startedAt,
263
+ error: String(error?.message || 'Failed to merge UI state'),
264
+ path: req.url || '/api/taskforce/ui-state',
265
+ stateKey: String(body?.stateKey || body?.key || '').trim() || 'default'
266
+ });
225
267
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
226
268
  res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to merge UI state') }));
227
269
  }
@@ -398,7 +440,7 @@ export function registerTaskRoutes(deps) {
398
440
  : body;
399
441
  const updated = core.updateTask(params.id, updates, workspaceId, {
400
442
  actorRef: 'user',
401
- skipTaskEvents: saveSource === 'autosave'
443
+ saveSource
402
444
  });
403
445
  if (!updated) {
404
446
  res.writeHead(404, { 'Content-Type': 'application/json' });
@@ -34,7 +34,7 @@ import { registerResourceRoutes } from './routes/resources.js';
34
34
  import { registerDocumentRoutes } from './routes/documents.js';
35
35
  import { registerDocumentReviewRoutes } from './routes/documentReviews.js';
36
36
  import { registerAnnotatedAttachmentRoutes } from './routes/annotatedAttachments.js';
37
- import { requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
37
+ import { buildCloudMcpRuntimeAuth, requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
38
38
  import { resolveDeploymentConfig } from '../config/deployment.js';
39
39
  import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../config/cloudEnvironment.js';
40
40
  import { getCanonicalDocumentName } from '../utils/documentNames.js';
@@ -372,7 +372,7 @@ export function createRoutes(core, context = {}) {
372
372
  ? 'Verify your Taskforce account'
373
373
  : 'Reset your Taskforce password';
374
374
  const text = kind === 'verify-email'
375
- ? `Welcome to Taskforce.\n\nVerify your email by opening this link:\n${link}\n\nIf you did not create an account, you can ignore this message.`
375
+ ? `Welcome to Taskforce.\n\nYou're almost in. Enter this verification token on the verification screen to activate your account:\n\n${token}\n\nPrefer the faster route? Open the link below to verify your email automatically:\n${link}\n\nIf you didn't create a Taskforce account, you can safely ignore this email.`
376
376
  : `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
377
377
  const result = await sendEmail({
378
378
  to: email,
@@ -1458,24 +1458,13 @@ export function createRoutes(core, context = {}) {
1458
1458
  serverName: 'taskforce-cloud',
1459
1459
  logStartup: false,
1460
1460
  runtimeMode: 'cloud',
1461
- auth: {
1462
- scopes: authScopes,
1463
- userId: auth.authenticatedViaToken?.userId || auth.authenticatedViaOAuth?.userId || auth.userId || null,
1464
- tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
1465
- aiProfileToken: auth.aiProfileToken,
1466
- aiProfileName: auth.aiProfileName,
1467
- aiProfileIcon: auth.aiProfileIcon,
1468
- aiProfileColor: auth.aiProfileColor,
1469
- aiProfileDescription: auth.aiProfileDescription,
1470
- aiProfileRole: auth.aiProfileRole,
1471
- aiProfileProvider: auth.aiProfileProvider,
1472
- aiProfileModel: auth.aiProfileModel,
1473
- aiProfileSurfaceType: auth.aiProfileSurfaceType,
1474
- actorType: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
1461
+ auth: buildCloudMcpRuntimeAuth({
1462
+ req,
1463
+ auth,
1464
+ requestHeader,
1475
1465
  requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
1476
- ip: req.socket?.remoteAddress || null,
1477
- userAgent: requestHeader(req, 'user-agent', '') || null,
1478
- },
1466
+ authScopes,
1467
+ }),
1479
1468
  });
1480
1469
  const transport = new StreamableHTTPServerTransport({
1481
1470
  sessionIdGenerator: undefined,
@@ -10,6 +10,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
10
10
  import * as mcpRuntime from '../mcp/runtime';
11
11
  import { createDatabaseAdapter } from '../storage/createDatabaseAdapter.js';
12
12
  import { ensureDocumentReferenceSchema, ensureTaskAssetsSchema, ensureWorkspaceAssetsSchema } from '../migrations/taskSchemaMigrations.js';
13
+ import { ObjectStorageClient } from '../storage/objectStorageClient.js';
13
14
  import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
14
15
  // Mock TaskforceCore
15
16
  vi.mock('../core/Taskforce', () => {
@@ -159,10 +160,13 @@ vi.mock('../core/Taskforce', () => {
159
160
  listDocumentReviewSessionsForSync = vi.fn().mockReturnValue([]);
160
161
  listDocumentReviewCommentsForSync = vi.fn().mockReturnValue([]);
161
162
  listAnnotatedAttachmentSessionsForSync = vi.fn().mockReturnValue([]);
163
+ listTaskEventsForSync = vi.fn().mockReturnValue([]);
164
+ listTaskEvents = vi.fn().mockReturnValue([]);
162
165
  getDocumentReviewSession = vi.fn().mockReturnValue(null);
163
166
  upsertAiProfileForSync = vi.fn();
164
167
  upsertDocumentReviewSessionForSync = vi.fn();
165
168
  upsertDocumentReviewCommentForSync = vi.fn();
169
+ upsertTaskEventForSync = vi.fn();
166
170
  deleteDocumentReviewSessionForSync = vi.fn().mockReturnValue(true);
167
171
  deleteDocumentReviewCommentForSync = vi.fn().mockReturnValue(true);
168
172
  deleteAnnotatedAttachmentSessionForSync = vi.fn().mockReturnValue(true);
@@ -434,6 +438,7 @@ vi.mock('../core/Taskforce', () => {
434
438
  ]);
435
439
  hasWorkspaceAdmin = vi.fn().mockReturnValue(true);
436
440
  registerPasswordUser = vi.fn();
441
+ isEmailVerified = vi.fn().mockReturnValue(false);
437
442
  requestEmailVerification = vi.fn().mockReturnValue({});
438
443
  verifyEmailWithToken = vi.fn().mockReturnValue(false);
439
444
  markEmailVerified = vi.fn().mockReturnValue(true);
@@ -892,6 +897,8 @@ describe('Server Routes', () => {
892
897
  auth: expect.objectContaining({
893
898
  aiProfileName: 'Automation Codex',
894
899
  aiProfileToken: 'tfp_automation_codex',
900
+ aiProfileIcon: 'Code2',
901
+ aiProfileColor: '#10a37f',
895
902
  aiProfileDescription: 'OpenAI Codex coding agent connected through MCP.',
896
903
  aiProfileRole: 'Coding Agent',
897
904
  aiProfileProvider: 'OpenAI',
@@ -1600,6 +1607,17 @@ describe('Server Routes', () => {
1600
1607
  });
1601
1608
  expect(core.updateUiState).not.toHaveBeenCalledWith('workspace-denied', expect.anything(), expect.anything());
1602
1609
  });
1610
+ it('POST /api/taskforce/ui-state should stamp debug response headers', async () => {
1611
+ const match = matchRoute('POST', '/api/taskforce/ui-state', routes);
1612
+ const req = createMockReq('POST', '/api/taskforce/ui-state', {
1613
+ stateKey: 'app',
1614
+ patch: { activeWorkspaceModule: 'tasks' }
1615
+ });
1616
+ const res = createMockRes();
1617
+ await match?.handler(req, res, {});
1618
+ expect(res.setHeader).toHaveBeenCalledWith('x-taskforce-ui-state-route', 'tasks');
1619
+ expect(res.setHeader).toHaveBeenCalledWith('x-taskforce-ui-state-request-id', expect.stringMatching(/^ui-/));
1620
+ });
1603
1621
  it('POST /api/taskforce/workspace-profile should persist workspace name and description', async () => {
1604
1622
  const match = matchRoute('POST', '/api/taskforce/workspace-profile', routes);
1605
1623
  const req = createMockReq('POST', '/api/taskforce/workspace-profile', { workspaceId: 'default', name: 'Acme Project', description: 'Customer onboarding' }, { 'x-taskforce-workspace-id': 'default' });
@@ -3821,7 +3839,7 @@ describe('Server Routes', () => {
3821
3839
  }));
3822
3840
  expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
3823
3841
  });
3824
- it('POST /api/taskforce/auth/register should return conflict when email exists', async () => {
3842
+ it('POST /api/taskforce/auth/register should route duplicate unverified email into verification recovery', async () => {
3825
3843
  const authConfig = {
3826
3844
  runtimeMode: 'cloud',
3827
3845
  enabled: true,
@@ -3835,6 +3853,8 @@ describe('Server Routes', () => {
3835
3853
  };
3836
3854
  routes = createRoutes(core, { authConfig });
3837
3855
  core.hasWorkspaceAdmin.mockReturnValueOnce(true);
3856
+ core.isEmailVerified.mockReturnValueOnce(false);
3857
+ core.requestEmailVerification.mockReturnValueOnce({ token: 'verify-token-existing' });
3838
3858
  const error = new Error('Email is already registered');
3839
3859
  error.statusCode = 409;
3840
3860
  error.code = 'EMAIL_ALREADY_EXISTS';
@@ -3849,6 +3869,49 @@ describe('Server Routes', () => {
3849
3869
  });
3850
3870
  const res = createMockRes();
3851
3871
  await match?.handler(req, res, {});
3872
+ expect(core.isEmailVerified).toHaveBeenCalledWith('owner@example.com');
3873
+ expect(core.requestEmailVerification).toHaveBeenCalledWith({ email: 'owner@example.com' });
3874
+ expect(res.writeHead).toHaveBeenCalledWith(409, expect.any(Object));
3875
+ const responsePayload = JSON.parse(String(res.end.mock.calls.at(-1)?.[0] || '{}'));
3876
+ expect(responsePayload).toEqual(expect.objectContaining({
3877
+ success: false,
3878
+ code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
3879
+ verificationRequired: true,
3880
+ emailSent: true,
3881
+ verificationToken: 'verify-token-existing'
3882
+ }));
3883
+ });
3884
+ it('POST /api/taskforce/auth/register should return conflict when verified email exists', async () => {
3885
+ const authConfig = {
3886
+ runtimeMode: 'cloud',
3887
+ enabled: true,
3888
+ requiredForApi: true,
3889
+ excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
3890
+ defaultRole: 'member',
3891
+ defaultWorkspaceId: 'default',
3892
+ sessionCookieName: 'taskforce_session',
3893
+ sessionSecret: 'test-secret',
3894
+ sessionTtlSeconds: 3600
3895
+ };
3896
+ routes = createRoutes(core, { authConfig });
3897
+ core.hasWorkspaceAdmin.mockReturnValueOnce(true);
3898
+ core.isEmailVerified.mockReturnValueOnce(true);
3899
+ const error = new Error('Email is already registered');
3900
+ error.statusCode = 409;
3901
+ error.code = 'EMAIL_ALREADY_EXISTS';
3902
+ core.registerPasswordUser.mockImplementationOnce(() => {
3903
+ throw error;
3904
+ });
3905
+ const match = matchRoute('POST', '/api/taskforce/auth/register', routes);
3906
+ const req = createMockReq('POST', '/api/taskforce/auth/register', {
3907
+ email: 'owner@example.com',
3908
+ password: 'password123',
3909
+ workspaceId: 'workspace-1'
3910
+ });
3911
+ const res = createMockRes();
3912
+ await match?.handler(req, res, {});
3913
+ expect(core.isEmailVerified).toHaveBeenCalledWith('owner@example.com');
3914
+ expect(core.requestEmailVerification).not.toHaveBeenCalled();
3852
3915
  expect(res.writeHead).toHaveBeenCalledWith(409, expect.any(Object));
3853
3916
  expect(res.end).toHaveBeenCalledWith(JSON.stringify({
3854
3917
  success: false,
@@ -6531,6 +6594,77 @@ describe('Server Routes', () => {
6531
6594
  });
6532
6595
  expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
6533
6596
  });
6597
+ it('POST /api/taskforce/sync/workspace/apply-local should apply task event upserts after task snapshots', async () => {
6598
+ core.resolvePreferredWorkspaceId.mockReturnValue('workspace-local');
6599
+ core.getTask.mockImplementation((taskId) => (taskId === 'task-sync-activity-1'
6600
+ ? {
6601
+ id: 'task-sync-activity-1',
6602
+ title: 'Synced task',
6603
+ status: 'done',
6604
+ category: 'general',
6605
+ type: 'task',
6606
+ createdAt: '2026-03-24T10:00:00.000Z',
6607
+ updatedAt: '2026-03-24T10:05:00.000Z',
6608
+ taskEvents: []
6609
+ }
6610
+ : null));
6611
+ routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
6612
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
6613
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
6614
+ changes: [
6615
+ {
6616
+ op: 'upsert',
6617
+ archived: false,
6618
+ task: {
6619
+ id: 'task-sync-activity-1',
6620
+ title: 'Synced task',
6621
+ status: 'done',
6622
+ category: 'general',
6623
+ type: 'task',
6624
+ updatedAt: '2026-03-24T10:05:00.000Z'
6625
+ }
6626
+ },
6627
+ {
6628
+ op: 'task-event-upsert',
6629
+ event: {
6630
+ id: 'task-event-sync-1',
6631
+ taskId: 'task-sync-activity-1',
6632
+ workspaceId: 'workspace-local',
6633
+ action: 'task-status-changed',
6634
+ actor: 'ai-profile-1',
6635
+ actorType: 'ai',
6636
+ createdAt: '2026-03-24T10:05:00.000Z',
6637
+ details: {
6638
+ changes: {
6639
+ status: {
6640
+ from: 'in-progress',
6641
+ to: 'done'
6642
+ }
6643
+ }
6644
+ }
6645
+ }
6646
+ }
6647
+ ]
6648
+ }, {
6649
+ 'x-taskforce-workspace-id': 'workspace-local'
6650
+ });
6651
+ const res = createMockRes();
6652
+ await match?.handler(req, res, {});
6653
+ expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
6654
+ tasks: [expect.objectContaining({ id: 'task-sync-activity-1' })],
6655
+ archived: []
6656
+ }), 'workspace-local');
6657
+ expect(core.upsertTaskEventForSync).toHaveBeenCalledWith(expect.objectContaining({
6658
+ id: 'task-event-sync-1',
6659
+ taskId: 'task-sync-activity-1',
6660
+ workspaceId: 'workspace-local',
6661
+ action: 'task-status-changed',
6662
+ actor: 'ai-profile-1',
6663
+ actorType: 'ai',
6664
+ createdAt: '2026-03-24T10:05:00.000Z'
6665
+ }));
6666
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
6667
+ });
6534
6668
  it('POST /api/taskforce/sync/workspace/apply-local should fail when document review session persistence fails', async () => {
6535
6669
  core.resolvePreferredWorkspaceId.mockReturnValue('workspace-local');
6536
6670
  const error = new Error('document review session write failed');
@@ -8771,6 +8905,73 @@ describe('Server Routes', () => {
8771
8905
  expect(payload.authenticated).toBe(true);
8772
8906
  expect(payload.avatarUrl).toBe('/api/taskforce/context/users%2Fu-avatar%2Fprofile%2Favatar%2Fdraft.png');
8773
8907
  });
8908
+ it('GET /api/taskforce/auth/session should not wait for avatar cleanup before responding', async () => {
8909
+ const authConfig = {
8910
+ runtimeMode: 'cloud',
8911
+ enabled: true,
8912
+ requiredForApi: true,
8913
+ excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
8914
+ defaultRole: 'member',
8915
+ defaultWorkspaceId: 'default',
8916
+ sessionCookieName: 'taskforce_session',
8917
+ sessionSecret: 'test-secret',
8918
+ sessionTtlSeconds: 3600
8919
+ };
8920
+ const deleteObjectCompletion = { resolve: null };
8921
+ const deleteObjectSpy = vi.spyOn(ObjectStorageClient.prototype, 'deleteObject').mockImplementation(() => new Promise((resolve) => {
8922
+ deleteObjectCompletion.resolve = resolve;
8923
+ }));
8924
+ routes = createRoutes(core, {
8925
+ authConfig,
8926
+ objectStorage: {
8927
+ provider: 'r2',
8928
+ r2: {
8929
+ accountId: 'test-account',
8930
+ bucket: 'taskforce-test',
8931
+ endpoint: 'https://example.com',
8932
+ accessKeyId: 'test-key',
8933
+ secretAccessKey: 'test-secret',
8934
+ keyPrefix: '',
8935
+ signedUploadTtlSeconds: 60,
8936
+ signedDownloadTtlSeconds: 60
8937
+ }
8938
+ }
8939
+ });
8940
+ core.cleanupStaleUserAvatarUploadDrafts.mockReturnValueOnce([{
8941
+ draftId: 'avatar-draft-stale',
8942
+ userId: 'u-avatar',
8943
+ storageKey: 'users/u-avatar/profile/avatar/avatar-draft-stale.png',
8944
+ discardedAt: null,
8945
+ consumedAt: null,
8946
+ finalizedAt: null,
8947
+ createdAt: '2026-04-14T00:00:00.000Z',
8948
+ updatedAt: '2026-04-14T00:00:00.000Z'
8949
+ }]);
8950
+ const cookie = createSessionTokenCookie('session-avatar-cleanup', authConfig, {
8951
+ userId: 'u-avatar',
8952
+ role: 'member',
8953
+ workspaceId: 'workspace-1'
8954
+ });
8955
+ const match = matchRoute('GET', '/api/taskforce/auth/session', routes);
8956
+ const req = createMockReq('GET', '/api/taskforce/auth/session', undefined, { cookie });
8957
+ const res = createMockRes();
8958
+ try {
8959
+ const outcome = await Promise.race([
8960
+ match?.handler(req, res, {}).then(() => 'completed'),
8961
+ new Promise((resolve) => setTimeout(() => resolve('timeout'), 25))
8962
+ ]);
8963
+ expect(outcome).toBe('completed');
8964
+ const payload = JSON.parse(res.end.mock.calls[0][0]);
8965
+ expect(payload.authenticated).toBe(true);
8966
+ expect(deleteObjectSpy).toHaveBeenCalledWith('users/u-avatar/profile/avatar/avatar-draft-stale.png');
8967
+ }
8968
+ finally {
8969
+ if (deleteObjectCompletion.resolve) {
8970
+ deleteObjectCompletion.resolve(true);
8971
+ }
8972
+ deleteObjectSpy.mockRestore();
8973
+ }
8974
+ });
8774
8975
  it('POST /api/taskforce/auth/profile should save avatar by validated draft id', async () => {
8775
8976
  const authConfig = {
8776
8977
  runtimeMode: 'cloud',
@@ -10300,6 +10501,43 @@ describe('Server Routes', () => {
10300
10501
  }
10301
10502
  });
10302
10503
  });
10504
+ it('POST /api/taskforce/admin/system-settings should preserve onboarding policy when onboarding fields are omitted', async () => {
10505
+ core.getOnboardingPolicy.mockReturnValue({
10506
+ mode: 'auto_grant_free',
10507
+ planVersionId: 'starter-v1'
10508
+ });
10509
+ const match = matchRoute('POST', '/api/taskforce/admin/system-settings', routes);
10510
+ const req = createMockReq('POST', '/api/taskforce/admin/system-settings', {
10511
+ allowSelfRegistration: false,
10512
+ requireVerifiedEmail: true,
10513
+ defaultNewUserBetaAccess: false,
10514
+ authRateLimitEnabled: true,
10515
+ authRateLimitMaxRequests: 10,
10516
+ authRateLimitWindowMs: 900000
10517
+ }, { 'x-taskforce-role': 'admin' });
10518
+ const res = createMockRes();
10519
+ await match?.handler(req, res, {});
10520
+ expect(core.validateOnboardingPolicy).toHaveBeenCalledWith({
10521
+ mode: 'auto_grant_free',
10522
+ planVersionId: 'starter-v1'
10523
+ });
10524
+ expect(core.updateGlobalSettings).toHaveBeenCalledWith({
10525
+ auth: {
10526
+ allowSelfRegistration: false,
10527
+ requireVerifiedEmail: true,
10528
+ defaultNewUserBetaAccess: false,
10529
+ authRateLimitEnabled: true,
10530
+ authRateLimitMaxRequests: 10,
10531
+ authRateLimitWindowMs: 900000
10532
+ },
10533
+ billing: {
10534
+ onboardingPolicy: {
10535
+ mode: 'auto_grant_free',
10536
+ planVersionId: 'starter-v1'
10537
+ }
10538
+ }
10539
+ });
10540
+ });
10303
10541
  it('GET /api/taskforce/admin/site-settings should expose effective website settings', async () => {
10304
10542
  core.getGlobalSettings.mockReturnValue({
10305
10543
  site: {
@@ -11571,7 +11809,7 @@ describe('Server Routes', () => {
11571
11809
  errors: []
11572
11810
  }));
11573
11811
  });
11574
- it('PATCH /api/taskforce/task/:id should pass autosave updates through as non-audited route metadata', async () => {
11812
+ it('PATCH /api/taskforce/task/:id should pass autosave metadata through to core.updateTask', async () => {
11575
11813
  const updatedTask = { id: '123', description: 'Draft body' };
11576
11814
  core.updateTask.mockReturnValue(updatedTask);
11577
11815
  const url = '/api/taskforce/task/123';
@@ -11581,12 +11819,12 @@ describe('Server Routes', () => {
11581
11819
  await match?.handler(req, res, { id: '123' });
11582
11820
  expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Draft body' }, 'default', {
11583
11821
  actorRef: 'user',
11584
- skipTaskEvents: true
11822
+ saveSource: 'autosave'
11585
11823
  });
11586
11824
  expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
11587
11825
  expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
11588
11826
  });
11589
- it('PATCH /api/taskforce/task/:id should keep manual updates on the audited path', async () => {
11827
+ it('PATCH /api/taskforce/task/:id should pass manual save metadata through to core.updateTask', async () => {
11590
11828
  const updatedTask = { id: '123', description: 'Manual body' };
11591
11829
  core.updateTask.mockReturnValue(updatedTask);
11592
11830
  const url = '/api/taskforce/task/123';
@@ -11596,7 +11834,7 @@ describe('Server Routes', () => {
11596
11834
  await match?.handler(req, res, { id: '123' });
11597
11835
  expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Manual body' }, 'default', {
11598
11836
  actorRef: 'user',
11599
- skipTaskEvents: false
11837
+ saveSource: 'manual'
11600
11838
  });
11601
11839
  expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
11602
11840
  expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
@@ -0,0 +1,10 @@
1
+ export interface TaskActor {
2
+ id: string;
3
+ kind: 'human' | 'ai';
4
+ label: string;
5
+ username: string;
6
+ icon: string;
7
+ color: string;
8
+ isNamed: boolean;
9
+ meta?: Record<string, unknown>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -39,6 +39,10 @@ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurface
39
39
  username: String(profile.username || '').trim(),
40
40
  icon: String(profile.icon || 'Bot').trim() || 'Bot',
41
41
  color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
42
+ description: toNullableTrimmedString(profile.description),
43
+ role: toNullableTrimmedString(profile.role),
44
+ provider: toNullableTrimmedString(profile.provider),
45
+ model: toNullableTrimmedString(profile.model),
42
46
  surfaceType: normalizeSurfaceType(profile.surfaceType, { source, profileId, workspaceId: profileWorkspaceId }),
43
47
  providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
44
48
  ? profile.providerMetadata
@@ -15,6 +15,10 @@ describe('collaborationSyncPayload', () => {
15
15
  username: ' agent ',
16
16
  icon: ' Bot ',
17
17
  color: ' #111111 ',
18
+ description: ' Product coding agent ',
19
+ role: ' Implementer ',
20
+ provider: ' OpenAI ',
21
+ model: ' gpt-5.4 ',
18
22
  surfaceType: 'agent',
19
23
  providerMetadata: { foo: 'bar' },
20
24
  createdAt: '2026-04-10T10:00:00.000Z',
@@ -29,6 +33,10 @@ describe('collaborationSyncPayload', () => {
29
33
  username: 'agent',
30
34
  icon: 'Bot',
31
35
  color: '#111111',
36
+ description: 'Product coding agent',
37
+ role: 'Implementer',
38
+ provider: 'OpenAI',
39
+ model: 'gpt-5.4',
32
40
  surfaceType: 'agent',
33
41
  providerMetadata: { foo: 'bar' },
34
42
  createdAt: '2026-04-10T10:00:00.000Z',
@@ -1,5 +1,5 @@
1
1
  import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
2
- import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
2
+ import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncTaskEventSnapshot } from './workspaceSyncModel.js';
3
3
  type SyncCore = TaskforceCore & TaskforceSyncCapable;
4
4
  interface CollaborationSyncStateDeps {
5
5
  core: SyncCore;
@@ -7,9 +7,11 @@ interface CollaborationSyncStateDeps {
7
7
  export declare function listWorkspaceSyncDocumentReviewSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewSessionSnapshot[];
8
8
  export declare function listWorkspaceSyncDocumentReviewCommentsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewCommentSnapshot[];
9
9
  export declare function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
10
+ export declare function listWorkspaceSyncTaskEventsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncTaskEventSnapshot[];
10
11
  export declare function createCollaborationSyncState(deps: CollaborationSyncStateDeps): {
11
12
  listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewSessionSnapshot[];
12
13
  listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewCommentSnapshot[];
13
14
  listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
15
+ listWorkspaceSyncTaskEventsSnapshot: (workspaceId?: string) => WorkspaceSyncTaskEventSnapshot[];
14
16
  };
15
17
  export {};
@@ -51,10 +51,29 @@ export function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, works
51
51
  && session.workspaceId.length > 0
52
52
  && session.baseImageAssetId.length > 0));
53
53
  }
54
+ export function listWorkspaceSyncTaskEventsSnapshot(deps, workspaceId = 'default') {
55
+ return deps.core.listTaskEventsForSync({ workspaceId })
56
+ .map((event) => ({
57
+ id: event.id,
58
+ taskId: event.taskId,
59
+ workspaceId: event.workspaceId,
60
+ action: event.action,
61
+ actor: event.actor,
62
+ actorType: event.actorType,
63
+ details: event.details,
64
+ createdAt: event.createdAt,
65
+ }))
66
+ .filter((event) => (event.id.length > 0
67
+ && event.taskId.length > 0
68
+ && event.workspaceId.length > 0
69
+ && event.action.length > 0
70
+ && event.createdAt.length > 0));
71
+ }
54
72
  export function createCollaborationSyncState(deps) {
55
73
  return {
56
74
  listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewSessionsSnapshot(deps, workspaceId),
57
75
  listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewCommentsSnapshot(deps, workspaceId),
58
76
  listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, workspaceId),
77
+ listWorkspaceSyncTaskEventsSnapshot: (workspaceId = 'default') => listWorkspaceSyncTaskEventsSnapshot(deps, workspaceId),
59
78
  };
60
79
  }