@taskforcehq/taskforce 0.3.302 → 0.3.304

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  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/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. package/dist/utils/billingStatusCache.js +0 -70
@@ -20,6 +20,7 @@
20
20
  * POST /api/taskforce/auth/invite/join
21
21
  * POST /api/taskforce/auth/logout
22
22
  * GET /api/taskforce/account/access-status
23
+ * GET /api/taskforce/account/profile-summary
23
24
  * GET /api/taskforce/account/entitlements
24
25
  * GET /api/taskforce/account/team-management-access
25
26
  * GET /api/taskforce/account/plan-preview
@@ -73,6 +74,121 @@ export function registerAuthRoutes(deps) {
73
74
  const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
74
75
  return teamAllowed && stateAllows ? 'team' : 'personal';
75
76
  };
77
+ const resolveBillingConflictCodeForUser = (userIdRaw, gateRaw) => {
78
+ const userId = String(userIdRaw || '').trim();
79
+ const gate = String(gateRaw || '').trim().toLowerCase();
80
+ if (!userId || !gate || gate === 'ok')
81
+ return null;
82
+ const db = core.getDatabaseAdapter();
83
+ const tenantId = core.getTenantId();
84
+ try {
85
+ const row = db.prepare(`
86
+ SELECT session_id, status, expires_at
87
+ FROM billing_checkout_attempts
88
+ WHERE tenant_id = ? AND user_id = ?
89
+ ORDER BY created_at DESC, session_id DESC
90
+ LIMIT 1
91
+ `).get(tenantId, userId);
92
+ const sessionId = String(row?.session_id || '').trim();
93
+ const status = String(row?.status || '').trim().toLowerCase();
94
+ const expiresAt = String(row?.expires_at || '').trim();
95
+ if (!sessionId)
96
+ return null;
97
+ if (status === 'open' && expiresAt) {
98
+ const expiresAtMs = Date.parse(expiresAt);
99
+ if (Number.isFinite(expiresAtMs) && expiresAtMs <= Date.now()) {
100
+ db.prepare(`
101
+ UPDATE billing_checkout_attempts
102
+ SET status = 'expired', updated_at = ?
103
+ WHERE tenant_id = ? AND session_id = ?
104
+ `).run(new Date().toISOString(), tenantId, sessionId);
105
+ return 'CHECKOUT_SESSION_EXPIRED';
106
+ }
107
+ }
108
+ if (status === 'expired')
109
+ return 'CHECKOUT_SESSION_EXPIRED';
110
+ if (status === 'superseded')
111
+ return 'CHECKOUT_SESSION_SUPERSEDED';
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ return null;
117
+ };
118
+ const buildAccountProfileSummary = (req) => {
119
+ const userId = requestUserId(req);
120
+ const workspaceId = requestWorkspaceId(req);
121
+ const access = core.getAccountAccessStatus(userId);
122
+ const resolvedAccess = core.resolveUserAccess(userId, workspaceId, 'member');
123
+ const hintedRole = normalizeTeamWorkspaceRole(requestHeader(req, 'x-taskforce-workspace-role', ''));
124
+ const workspaceRole = normalizeTeamWorkspaceRole(resolvedAccess?.role || hintedRole || null);
125
+ const canManageWorkspace = workspaceRole === 'owner' || workspaceRole === 'admin';
126
+ const db = core.getDatabaseAdapter();
127
+ const tenantId = core.getTenantId();
128
+ let billingRow;
129
+ try {
130
+ billingRow = db.prepare(`
131
+ SELECT stripe_status, stripe_customer_id, stripe_subscription_id, stripe_price_id, billing_interval,
132
+ trial_end, current_period_end, effective_until, entitlement_state, tier
133
+ FROM user_billing
134
+ WHERE tenant_id = ? AND user_id = ?
135
+ LIMIT 1
136
+ `).get(tenantId, userId);
137
+ }
138
+ catch {
139
+ billingRow = undefined;
140
+ }
141
+ const planId = String(access.entitlement?.planId || billingRow?.tier || '').trim() || null;
142
+ const planVersionId = String(access.entitlement?.planVersionId || '').trim() || null;
143
+ let planRow;
144
+ if (planId) {
145
+ try {
146
+ planRow = db.prepare(`
147
+ SELECT display_name
148
+ FROM plan_catalog
149
+ WHERE tenant_id = ? AND plan_id = ?
150
+ LIMIT 1
151
+ `).get(tenantId, planId);
152
+ }
153
+ catch {
154
+ planRow = undefined;
155
+ }
156
+ }
157
+ let teamPlanMode = 'unknown';
158
+ try {
159
+ const entitlements = core.getAccountEntitlementsPayload({ userId, workspaceId });
160
+ teamPlanMode = resolveTeamPlanModeFromEntitlements(entitlements);
161
+ }
162
+ catch (error) {
163
+ if (error?.code === 'ENTITLEMENT_NOT_FOUND' && (access.gate === 'plan_selection_required' || access.gate === 'checkout_pending')) {
164
+ teamPlanMode = 'personal';
165
+ }
166
+ }
167
+ const billingConflictCode = resolveBillingConflictCodeForUser(userId, access.gate);
168
+ return {
169
+ stripeStatus: billingRow?.stripe_status || null,
170
+ stripeCustomerId: billingRow?.stripe_customer_id || null,
171
+ stripeSubscriptionId: billingRow?.stripe_subscription_id || null,
172
+ stripePriceId: billingRow?.stripe_price_id || null,
173
+ billingInterval: billingRow?.billing_interval || null,
174
+ trialEnd: billingRow?.trial_end || null,
175
+ currentPeriodEnd: billingRow?.current_period_end || null,
176
+ effectiveUntil: billingRow?.effective_until || null,
177
+ entitlementState: access.entitlement?.state || billingRow?.entitlement_state || null,
178
+ planVersionId,
179
+ planId,
180
+ planName: planRow?.display_name ? String(planRow.display_name) : planId,
181
+ gate: access.gate,
182
+ message: access.message,
183
+ billingConflictCode,
184
+ planSelectionRequired: access.planSelectionRequired,
185
+ workspaceId,
186
+ workspaceRole,
187
+ canManageWorkspace,
188
+ teamPlanMode,
189
+ teamManagementAllowed: canManageWorkspace && teamPlanMode === 'team'
190
+ };
191
+ };
76
192
  // === SECTION: Email delivery helpers ===
77
193
  const getEmailConfig = () => {
78
194
  if (context.authConfig?.runtimeMode !== 'cloud') {
@@ -86,7 +202,7 @@ export function registerAuthRoutes(deps) {
86
202
  const authSettings = globalSettings.auth || {};
87
203
  const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
88
204
  const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
89
- const signupSettings = core.getSignupMonetizationSettings();
205
+ const onboardingPolicy = core.getOnboardingPolicy();
90
206
  return {
91
207
  allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
92
208
  requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
@@ -98,8 +214,8 @@ export function registerAuthRoutes(deps) {
98
214
  authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
99
215
  ? Math.floor(authRateLimitWindowMsRaw)
100
216
  : 15 * 60_000,
101
- signupMonetizationStrategy: signupSettings.strategy,
102
- defaultSignupPlanVersionId: signupSettings.defaultSignupPlanVersionId
217
+ onboardingPolicyMode: onboardingPolicy.mode,
218
+ onboardingPlanVersionId: onboardingPolicy.planVersionId
103
219
  };
104
220
  };
105
221
  const getSiteBehavior = () => {
@@ -110,6 +226,23 @@ export function registerAuthRoutes(deps) {
110
226
  pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
111
227
  };
112
228
  };
229
+ const buildPasswordSessionCookieForUser = (req, authConfig, userIdRaw, options) => {
230
+ const userId = String(userIdRaw || '').trim();
231
+ const fallbackRole = String(options?.fallbackRole || authConfig.defaultRole || 'member').trim() || 'member';
232
+ const resolvedWorkspaceId = core.resolvePreferredUserWorkspaceId(userId, {
233
+ preferredWorkspaceId: options?.preferredWorkspaceId || null,
234
+ persistedWorkspaceId: options?.persistedWorkspaceId || null
235
+ });
236
+ const access = resolvedWorkspaceId
237
+ ? core.resolveUserAccess(userId, resolvedWorkspaceId, fallbackRole)
238
+ : null;
239
+ return createSessionTokenCookie(`pwd:${userId}`, authConfig, {
240
+ role: (access?.role || fallbackRole),
241
+ workspaceId: access?.workspaceId || '',
242
+ userId,
243
+ ...resolveSessionCookieAttributesForRequest(req, authConfig)
244
+ });
245
+ };
113
246
  const AVATAR_DRAFT_RETENTION_MS = 24 * 60 * 60 * 1000;
114
247
  const MAX_AVATAR_UPLOAD_BYTES = 5 * 1024 * 1024;
115
248
  const AVATAR_MIME_EXTENSION = {
@@ -175,7 +308,7 @@ export function registerAuthRoutes(deps) {
175
308
  ? 'Verify your Taskforce account'
176
309
  : 'Reset your Taskforce password';
177
310
  const text = kind === 'verify-email'
178
- ? `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.`
311
+ ? `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.`
179
312
  : `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
180
313
  const result = await sendEmail({
181
314
  to: email,
@@ -268,7 +401,8 @@ export function registerAuthRoutes(deps) {
268
401
  let avatarUrl = null;
269
402
  let betaAccess = true;
270
403
  if (authenticated && access?.userId) {
271
- await cleanupStaleAvatarDrafts(access.userId);
404
+ // Keep the session endpoint fast on reload; avatar draft cleanup is best-effort housekeeping.
405
+ void cleanupStaleAvatarDrafts(access.userId).catch(() => { });
272
406
  betaAccess = core.getUserBetaAccess(access.userId);
273
407
  const resolved = core.resolveUserAccess(access.userId, workspaceId, role);
274
408
  if (resolved) {
@@ -643,7 +777,7 @@ export function registerAuthRoutes(deps) {
643
777
  res.writeHead(400, { 'Content-Type': 'application/json' });
644
778
  res.end(JSON.stringify({ success: false, error: 'Email and password are required.' }));
645
779
  });
646
- // POST /api/taskforce/auth/register - Create account and exchange for secure session cookie
780
+ // POST /api/taskforce/auth/register - Create account and optionally start a session
647
781
  addRoute('POST', '/api/taskforce/auth/register', async (req, res) => {
648
782
  if (!requireCloudRuntime(res, 'Authentication'))
649
783
  return;
@@ -693,13 +827,6 @@ export function registerAuthRoutes(deps) {
693
827
  role,
694
828
  betaAccess: authBehavior.defaultNewUserBetaAccess
695
829
  });
696
- const sessionWorkspaceId = created.workspaceId || '';
697
- const cookie = createSessionTokenCookie(`pwd:${created.userId}`, authConfig, {
698
- role: created.role,
699
- workspaceId: sessionWorkspaceId,
700
- userId: created.userId,
701
- ...resolveSessionCookieAttributesForRequest(req, authConfig)
702
- });
703
830
  let emailSent = true;
704
831
  let emailError;
705
832
  let verificationToken;
@@ -719,7 +846,13 @@ export function registerAuthRoutes(deps) {
719
846
  role: created.role,
720
847
  emailSent
721
848
  });
722
- res.setHeader('Set-Cookie', cookie);
849
+ if (!authBehavior.requireVerifiedEmail) {
850
+ res.setHeader('Set-Cookie', buildPasswordSessionCookieForUser(req, authConfig, created.userId, {
851
+ preferredWorkspaceId: created.workspaceId || null,
852
+ persistedWorkspaceId: created.workspaceId || null,
853
+ fallbackRole: created.role
854
+ }));
855
+ }
723
856
  res.writeHead(201, { 'Content-Type': 'application/json' });
724
857
  res.end(JSON.stringify({
725
858
  success: true,
@@ -727,6 +860,8 @@ export function registerAuthRoutes(deps) {
727
860
  role: created.role,
728
861
  workspaceSetupRequired: !created.workspaceId,
729
862
  planSelectionRequired: created.planSelectionRequired === true,
863
+ checkoutPending: created.checkoutPending === true,
864
+ commercialState: created.commercialState,
730
865
  verificationRequired: authBehavior.requireVerifiedEmail,
731
866
  emailSent,
732
867
  emailError,
@@ -736,6 +871,40 @@ export function registerAuthRoutes(deps) {
736
871
  }));
737
872
  }
738
873
  catch (error) {
874
+ if (error?.code === 'EMAIL_ALREADY_EXISTS'
875
+ && authBehavior.requireVerifiedEmail
876
+ && email
877
+ && !core.isEmailVerified(email)) {
878
+ let emailSent = true;
879
+ let emailError;
880
+ let verificationToken;
881
+ const verification = core.requestEmailVerification({ email });
882
+ verificationToken = verification.token || null;
883
+ if (verification.token) {
884
+ const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
885
+ emailSent = delivery.ok;
886
+ emailError = delivery.error;
887
+ }
888
+ auditAuthAction(req, 'auth-register-existing-unverified', {
889
+ email,
890
+ workspaceId: workspaceIdInput || null,
891
+ code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
892
+ emailSent
893
+ });
894
+ res.writeHead(409, { 'Content-Type': 'application/json' });
895
+ res.end(JSON.stringify({
896
+ success: false,
897
+ error: 'This email already has an unverified account. Continue with email verification instead.',
898
+ code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
899
+ verificationRequired: true,
900
+ emailSent,
901
+ emailError,
902
+ verificationToken: shouldExposeDevAuthTokens()
903
+ ? (verificationToken || null)
904
+ : undefined
905
+ }));
906
+ return;
907
+ }
739
908
  const statusCode = Number(error?.statusCode || 400);
740
909
  const message = String(error?.message || 'Unable to register');
741
910
  auditAuthAction(req, 'auth-register-failed', {
@@ -794,6 +963,12 @@ export function registerAuthRoutes(deps) {
794
963
  addRoute('POST', '/api/taskforce/auth/verify-email/confirm', async (req, res) => {
795
964
  if (!requireCloudRuntime(res, 'Authentication'))
796
965
  return;
966
+ const authConfig = context.authConfig;
967
+ if (!authConfig?.enabled) {
968
+ res.writeHead(400, { 'Content-Type': 'application/json' });
969
+ res.end(JSON.stringify({ success: false, error: 'Authentication is not configured.' }));
970
+ return;
971
+ }
797
972
  const body = await parseJsonBody(req);
798
973
  const token = String(body?.token || '').trim();
799
974
  if (!token) {
@@ -801,6 +976,15 @@ export function registerAuthRoutes(deps) {
801
976
  res.end(JSON.stringify({ success: false, error: 'Token is required.' }));
802
977
  return;
803
978
  }
979
+ const inspection = core.inspectOneTimeAuthToken(token, 'email_verification');
980
+ if (!inspection.userId) {
981
+ auditAuthAction(req, 'auth-verify-confirm-failed', {
982
+ tokenProvided: true
983
+ });
984
+ res.writeHead(400, { 'Content-Type': 'application/json' });
985
+ res.end(JSON.stringify({ success: false, error: 'Invalid or expired token.' }));
986
+ return;
987
+ }
804
988
  const verified = core.verifyEmailWithToken(token);
805
989
  if (!verified) {
806
990
  auditAuthAction(req, 'auth-verify-confirm-failed', {
@@ -813,6 +997,11 @@ export function registerAuthRoutes(deps) {
813
997
  auditAuthAction(req, 'auth-verify-confirm-success', {
814
998
  tokenProvided: true
815
999
  });
1000
+ const cookie = buildPasswordSessionCookieForUser(req, authConfig, inspection.userId, {
1001
+ preferredWorkspaceId: inspection.workspaceId || null,
1002
+ persistedWorkspaceId: inspection.workspaceId || null
1003
+ });
1004
+ res.setHeader('Set-Cookie', cookie);
816
1005
  res.writeHead(200, { 'Content-Type': 'application/json' });
817
1006
  res.end(JSON.stringify({ success: true }));
818
1007
  });
@@ -1098,6 +1287,24 @@ export function registerAuthRoutes(deps) {
1098
1287
  res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve account access status'), code: error?.code, details: error?.details }));
1099
1288
  }
1100
1289
  });
1290
+ // GET /api/taskforce/account/profile-summary - Fast authoritative summary for the visible account surface.
1291
+ addRoute('GET', '/api/taskforce/account/profile-summary', async (req, res) => {
1292
+ if (!ensureAuthenticatedUser(req)) {
1293
+ res.writeHead(401, { 'Content-Type': 'application/json' });
1294
+ res.end(JSON.stringify({ error: 'Authentication required.' }));
1295
+ return;
1296
+ }
1297
+ try {
1298
+ const payload = buildAccountProfileSummary(req);
1299
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1300
+ res.end(JSON.stringify(payload));
1301
+ }
1302
+ catch (error) {
1303
+ const statusCode = resolveErrorStatusCode(error, 400);
1304
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1305
+ res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve account profile summary'), code: error?.code, details: error?.details }));
1306
+ }
1307
+ });
1101
1308
  // GET /api/taskforce/account/entitlements - Resolve effective plan/features for the active workspace.
1102
1309
  addRoute('GET', '/api/taskforce/account/entitlements', async (req, res) => {
1103
1310
  if (!ensureAuthenticatedUser(req)) {
@@ -1115,11 +1322,11 @@ export function registerAuthRoutes(deps) {
1115
1322
  catch (error) {
1116
1323
  if (error?.code === 'ENTITLEMENT_NOT_FOUND') {
1117
1324
  const access = core.getAccountAccessStatus(requestUserId(req));
1118
- if (access.gate === 'plan_selection_required') {
1325
+ if (access.gate === 'plan_selection_required' || access.gate === 'checkout_pending') {
1119
1326
  res.writeHead(403, { 'Content-Type': 'application/json' });
1120
1327
  res.end(JSON.stringify({
1121
1328
  error: access.message,
1122
- code: 'PLAN_SELECTION_REQUIRED',
1329
+ code: access.gate === 'checkout_pending' ? 'CHECKOUT_PENDING' : 'PLAN_SELECTION_REQUIRED',
1123
1330
  details: {
1124
1331
  gate: access.gate,
1125
1332
  planSelectionRequired: access.planSelectionRequired,
@@ -1141,51 +1348,20 @@ export function registerAuthRoutes(deps) {
1141
1348
  res.end(JSON.stringify({ error: 'Authentication required.' }));
1142
1349
  return;
1143
1350
  }
1144
- const wid = requestWorkspaceId(req);
1145
- const userId = requestUserId(req);
1146
- const hintedRole = normalizeTeamWorkspaceRole(requestHeader(req, 'x-taskforce-workspace-role', ''));
1147
- const resolvedAccess = core.resolveUserAccess(userId, wid, 'member');
1148
- const role = normalizeTeamWorkspaceRole(resolvedAccess?.role || hintedRole || null);
1149
- const canManageWorkspace = role === 'owner' || role === 'admin';
1150
- if (!canManageWorkspace) {
1151
- res.writeHead(200, { 'Content-Type': 'application/json' });
1152
- res.end(JSON.stringify({
1153
- workspaceId: wid,
1154
- role,
1155
- canManageWorkspace: false,
1156
- teamPlanMode: 'unknown',
1157
- allowed: false
1158
- }));
1159
- return;
1160
- }
1161
1351
  try {
1162
- const entitlements = core.getAccountEntitlementsPayload({ userId, workspaceId: wid });
1163
- const teamPlanMode = resolveTeamPlanModeFromEntitlements(entitlements);
1352
+ const payload = buildAccountProfileSummary(req);
1164
1353
  res.writeHead(200, { 'Content-Type': 'application/json' });
1165
1354
  res.end(JSON.stringify({
1166
- workspaceId: wid,
1167
- role,
1168
- canManageWorkspace: true,
1169
- teamPlanMode,
1170
- allowed: teamPlanMode === 'team'
1355
+ workspaceId: payload.workspaceId,
1356
+ role: payload.workspaceRole,
1357
+ canManageWorkspace: payload.canManageWorkspace,
1358
+ teamPlanMode: payload.teamPlanMode,
1359
+ allowed: payload.teamManagementAllowed,
1360
+ gate: payload.gate,
1361
+ planSelectionRequired: payload.planSelectionRequired
1171
1362
  }));
1172
1363
  }
1173
1364
  catch (error) {
1174
- if (error?.code === 'ENTITLEMENT_NOT_FOUND') {
1175
- const access = core.getAccountAccessStatus(userId);
1176
- const teamPlanMode = access.gate === 'plan_selection_required' ? 'personal' : 'unknown';
1177
- res.writeHead(200, { 'Content-Type': 'application/json' });
1178
- res.end(JSON.stringify({
1179
- workspaceId: wid,
1180
- role,
1181
- canManageWorkspace: true,
1182
- teamPlanMode,
1183
- allowed: false,
1184
- gate: access.gate,
1185
- planSelectionRequired: access.planSelectionRequired
1186
- }));
1187
- return;
1188
- }
1189
1365
  const statusCode = resolveErrorStatusCode(error, 400);
1190
1366
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1191
1367
  res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve team management access'), code: error?.code, details: error?.details }));