@taskforcehq/taskforce 0.3.306 → 0.3.307

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 (215) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.js +9 -1
  38. package/dist/components/views/PlanComparisonPage.test.js +19 -0
  39. package/dist/components/views/PlansPage.js +1 -1
  40. package/dist/components/views/StandaloneLayout.js +256 -69
  41. package/dist/components/views/WidgetView.js +10 -8
  42. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  43. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  44. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  45. package/dist/config/envSchema.js +1 -0
  46. package/dist/core/AiProfileService.d.ts +7 -0
  47. package/dist/core/AiProfileService.js +460 -35
  48. package/dist/core/AiProfiles.test.js +648 -2
  49. package/dist/core/AttachmentLinkService.d.ts +3 -0
  50. package/dist/core/AttachmentLinkService.js +58 -13
  51. package/dist/core/GlobalSettingsService.js +12 -0
  52. package/dist/core/McpTokenService.d.ts +12 -0
  53. package/dist/core/McpTokenService.js +44 -1
  54. package/dist/core/PlanningEntities.test.js +366 -4
  55. package/dist/core/TaskActivityService.d.ts +28 -1
  56. package/dist/core/TaskActivityService.js +227 -46
  57. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  58. package/dist/core/TaskActivitySync.test.js +90 -0
  59. package/dist/core/TaskAuditTimeline.test.js +96 -0
  60. package/dist/core/Taskforce.d.ts +190 -26
  61. package/dist/core/Taskforce.ids.test.js +58 -0
  62. package/dist/core/Taskforce.js +775 -71
  63. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  64. package/dist/core/types.d.ts +7 -0
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  66. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  67. package/dist/hooks/bootstrapTimeouts.js +8 -0
  68. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  69. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  70. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  71. package/dist/hooks/sync/orchestratorShared.js +61 -1
  72. package/dist/hooks/sync/recovery.js +8 -0
  73. package/dist/hooks/sync/recovery.test.js +5 -5
  74. package/dist/hooks/sync/transfers.d.ts +2 -1
  75. package/dist/hooks/sync/transfers.js +36 -9
  76. package/dist/hooks/sync/transfers.test.js +534 -2
  77. package/dist/hooks/useFilterSort.js +13 -13
  78. package/dist/hooks/useFilterSort.test.js +87 -1
  79. package/dist/hooks/useSyncOrchestrator.js +50 -5
  80. package/dist/hooks/useTaskforce.d.ts +85 -50
  81. package/dist/hooks/useTaskforce.js +44 -14
  82. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  83. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  84. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  85. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  86. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  87. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  88. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  89. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  90. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  91. package/dist/mcp/clientIntegrations.d.ts +1 -0
  92. package/dist/mcp/clientIntegrations.js +25 -0
  93. package/dist/mcp/clientIntegrations.test.js +14 -3
  94. package/dist/mcp/clientProfileDefaults.js +3 -3
  95. package/dist/mcp/collaborationRegistrar.js +7 -7
  96. package/dist/mcp/documentAssetRegistrar.js +113 -7
  97. package/dist/mcp/runtime.d.ts +1 -0
  98. package/dist/mcp/runtime.js +969 -122
  99. package/dist/mcp/runtime.test.js +1083 -11
  100. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  101. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  102. package/dist/mcp/toolRegistry.d.ts +56 -0
  103. package/dist/mcp/toolRegistry.js +8 -0
  104. package/dist/mcp/toolRegistry.test.js +4 -0
  105. package/dist/migrations/taskSchemaMigrations.js +21 -0
  106. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  107. package/dist/server/aiProfileAvatarAssets.js +319 -0
  108. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  109. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  110. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/index.test.js +6 -1
  113. package/dist/server/routes/admin.js +187 -9
  114. package/dist/server/routes/annotatedAttachments.js +51 -0
  115. package/dist/server/routes/auth.d.ts +11 -0
  116. package/dist/server/routes/auth.js +189 -27
  117. package/dist/server/routes/authSupport.d.ts +4 -0
  118. package/dist/server/routes/authSupport.js +12 -1
  119. package/dist/server/routes/billing.js +11 -0
  120. package/dist/server/routes/billing.test.js +3 -0
  121. package/dist/server/routes/documents.js +10 -0
  122. package/dist/server/routes/shared.js +0 -10
  123. package/dist/server/routes/sync.integration.test.js +221 -0
  124. package/dist/server/routes/sync.js +13 -3
  125. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  126. package/dist/server/routes/tasks.js +12 -9
  127. package/dist/server/routes.js +87 -8
  128. package/dist/server/routes.test.js +890 -22
  129. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  130. package/dist/shared/aiProfileSurfaceType.js +10 -8
  131. package/dist/storage/objectStorage.d.ts +1 -0
  132. package/dist/storage/objectStorage.js +2 -1
  133. package/dist/storage/objectStorageClient.d.ts +1 -0
  134. package/dist/storage/objectStorageClient.js +41 -8
  135. package/dist/storage/objectStorageClient.test.js +26 -1
  136. package/dist/storage/objectStorageConfig.js +3 -1
  137. package/dist/storage/objectStorageConfig.test.js +13 -1
  138. package/dist/sync/collaborationSyncPayload.js +2 -0
  139. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  140. package/dist/sync/collaborationSyncState.d.ts +3 -3
  141. package/dist/sync/collaborationSyncState.js +19 -12
  142. package/dist/sync/contentSyncPayload.d.ts +5 -1
  143. package/dist/sync/contentSyncPayload.js +9 -0
  144. package/dist/sync/contentSyncPayload.test.js +39 -0
  145. package/dist/sync/contentSyncState.d.ts +5 -1
  146. package/dist/sync/contentSyncState.js +231 -37
  147. package/dist/sync/planningSyncPayload.js +5 -0
  148. package/dist/sync/planningSyncPayload.test.js +24 -0
  149. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  150. package/dist/sync/syncApplyHandlers.js +89 -31
  151. package/dist/sync/syncApplyHandlers.test.js +184 -4
  152. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  153. package/dist/sync/syncResourceAdapters.js +2 -0
  154. package/dist/sync/syncService.d.ts +1 -0
  155. package/dist/sync/syncService.js +13 -1
  156. package/dist/sync/syncService.test.js +39 -0
  157. package/dist/sync/workspacePullFeed.d.ts +4 -0
  158. package/dist/sync/workspacePullFeed.js +129 -70
  159. package/dist/sync/workspacePullFeed.test.js +49 -0
  160. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  161. package/dist/sync/workspaceSyncModel.js +159 -45
  162. package/dist/sync/workspaceSyncModel.test.js +269 -3
  163. package/dist/types.d.ts +33 -2
  164. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  165. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  166. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  167. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  168. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  169. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  170. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  171. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  172. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
  173. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  174. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  175. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  176. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  177. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  178. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  179. package/dist/ui/assets/index-607WPo_X.js +4 -0
  180. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  181. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  182. package/dist/ui/index.html +3 -3
  183. package/dist/ui/product/agent manager.png +0 -0
  184. package/dist/ui/product/agent_manager_02.png +0 -0
  185. package/dist/utils/aiProfileEvents.d.ts +7 -0
  186. package/dist/utils/aiProfileEvents.js +6 -0
  187. package/dist/utils/assignees.d.ts +6 -0
  188. package/dist/utils/assignees.js +8 -1
  189. package/dist/utils/assignees.test.d.ts +1 -0
  190. package/dist/utils/assignees.test.js +25 -0
  191. package/dist/utils/avatarDisplay.d.ts +1 -0
  192. package/dist/utils/avatarDisplay.js +15 -0
  193. package/dist/utils/contextFiles.d.ts +3 -1
  194. package/dist/utils/contextFiles.js +2 -1
  195. package/dist/utils/taskActivity.d.ts +7 -1
  196. package/dist/utils/taskActivity.js +63 -1
  197. package/dist/utils/taskActivity.test.js +23 -1
  198. package/dist/utils/taskSearch.d.ts +22 -0
  199. package/dist/utils/taskSearch.js +87 -0
  200. package/dist/utils/taskSearch.test.d.ts +1 -0
  201. package/dist/utils/taskSearch.test.js +69 -0
  202. package/dist/utils/taskUtils.js +10 -3
  203. package/dist/utils/taskUtils.test.d.ts +1 -0
  204. package/dist/utils/taskUtils.test.js +19 -0
  205. package/package.json +1 -1
  206. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  207. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  208. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  209. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  210. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  211. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  212. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  213. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  214. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  215. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -79,6 +79,26 @@ export function registerAdminRoutes(deps) {
79
79
  const workspaceAssetStore = typeof core.getDatabaseAdapter === 'function' && typeof core.getTenantId === 'function'
80
80
  ? new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId())
81
81
  : null;
82
+ const isAiProfileAvatarStorageKey = (storageKey, workspaceId) => {
83
+ const normalized = String(storageKey || '').replace(/\\/g, '/');
84
+ return normalized.startsWith(`workspaces/${workspaceId}/assets/images/ai-profiles/`)
85
+ && normalized.includes('/avatar/');
86
+ };
87
+ const listReferencedAiProfileAvatarStorageKeys = (workspaceId) => {
88
+ const referenced = new Set();
89
+ const profiles = typeof core.listAiProfiles === 'function'
90
+ ? core.listAiProfiles(workspaceId, { includeArchived: true })
91
+ : [];
92
+ for (const profile of Array.isArray(profiles) ? profiles : []) {
93
+ for (const value of [profile?.avatarUrl, profile?.avatarSourceUrl]) {
94
+ const storageKey = normalizeDocumentPathInput(value);
95
+ if (!storageKey || !isAiProfileAvatarStorageKey(storageKey, workspaceId))
96
+ continue;
97
+ referenced.add(storageKey);
98
+ }
99
+ }
100
+ return referenced;
101
+ };
82
102
  // === SECTION: Email helpers ===
83
103
  const resolveEffectiveVirusScanSettings = () => {
84
104
  const settings = core.getGlobalSettings().objectStorage || {};
@@ -95,11 +115,15 @@ export function registerAdminRoutes(deps) {
95
115
  };
96
116
  // === SECTION: MCP helpers ===
97
117
  const MCP_CONNECTION_TEST_TIMEOUT_MS = 5000;
98
- const writeMcpHttpError = (res, statusCode, message) => {
118
+ const writeMcpHttpError = (res, statusCode, message, data) => {
99
119
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
100
120
  res.end(JSON.stringify({
101
121
  jsonrpc: '2.0',
102
- error: { code: -32000, message },
122
+ error: {
123
+ code: -32000,
124
+ message,
125
+ ...(data ? { data } : {}),
126
+ },
103
127
  id: null,
104
128
  }));
105
129
  };
@@ -394,6 +418,8 @@ export function registerAdminRoutes(deps) {
394
418
  authenticatedViaOAuth: oauthAuth || null,
395
419
  userId: null,
396
420
  ...bootstrap,
421
+ aiProfileToken: bootstrap.aiProfileToken || oauthAuth?.aiProfileToken || null,
422
+ aiProfileName: bootstrap.aiProfileName || oauthAuth?.aiProfileName || null,
397
423
  };
398
424
  }
399
425
  core.addMcpAuditLog({
@@ -506,6 +532,7 @@ export function registerAdminRoutes(deps) {
506
532
  return;
507
533
  }
508
534
  const authScopes = auth.authenticatedViaToken?.scopes || auth.authenticatedViaOAuth?.scopes || [];
535
+ const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID();
509
536
  const deniedToolName = findDeniedCloudMcpToolName(body, authScopes);
510
537
  if (deniedToolName) {
511
538
  core.addMcpAuditLog({
@@ -515,7 +542,7 @@ export function registerAdminRoutes(deps) {
515
542
  tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
516
543
  toolName: deniedToolName,
517
544
  details: {
518
- requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
545
+ requestId,
519
546
  ip: req.socket?.remoteAddress || null,
520
547
  userAgent: requestHeader(req, 'user-agent', '') || null,
521
548
  outcome: 'deny',
@@ -528,11 +555,13 @@ export function registerAdminRoutes(deps) {
528
555
  }
529
556
  if (!applyCloudMcpWriteRateLimit(req, res, auth, body))
530
557
  return;
558
+ let runtime = null;
531
559
  try {
532
- const runtime = createTaskforceMcpServer({
560
+ runtime = createTaskforceMcpServer({
533
561
  projectRoot: core.getPaths().projectRoot,
534
562
  tenantId: core.getTenantId(),
535
563
  workspaceId: auth.workspaceId,
564
+ core,
536
565
  serverName: 'taskforce-cloud',
537
566
  logStartup: false,
538
567
  runtimeMode: 'cloud',
@@ -540,7 +569,7 @@ export function registerAdminRoutes(deps) {
540
569
  req,
541
570
  auth,
542
571
  requestHeader,
543
- requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
572
+ requestId,
544
573
  authScopes,
545
574
  }),
546
575
  });
@@ -557,8 +586,27 @@ export function registerAdminRoutes(deps) {
557
586
  recordCloudMcpCredentialUsage(req, auth.authenticatedViaToken, auth.authenticatedViaOAuth);
558
587
  }
559
588
  catch (error) {
589
+ console.error('[Taskforce MCP] Cloud transport request failed:', {
590
+ requestId,
591
+ kind: 'tool_transport_failure',
592
+ message: error instanceof Error ? error.message : String(error),
593
+ });
560
594
  if (!res.headersSent) {
561
- writeMcpHttpError(res, 500, 'Internal server error.');
595
+ writeMcpHttpError(res, 500, 'MCP transport failure.', {
596
+ kind: 'tool_transport_failure',
597
+ retryable: true,
598
+ requestId,
599
+ });
600
+ }
601
+ }
602
+ finally {
603
+ if (runtime) {
604
+ try {
605
+ await runtime.server.close();
606
+ }
607
+ catch (closeError) {
608
+ console.warn('[Taskforce MCP] Cloud transport cleanup failed:', closeError);
609
+ }
562
610
  }
563
611
  }
564
612
  };
@@ -1231,8 +1279,10 @@ export function registerAdminRoutes(deps) {
1231
1279
  }
1232
1280
  const globalSettings = core.getGlobalSettings();
1233
1281
  const authSettings = globalSettings.auth || {};
1282
+ const siteSettings = globalSettings.site || {};
1234
1283
  const onboardingPolicy = core.getOnboardingPolicy();
1235
1284
  const effective = buildAuthBehavior(core);
1285
+ const effectiveSite = buildSiteBehavior(core);
1236
1286
  res.writeHead(200, { 'Content-Type': 'application/json' });
1237
1287
  res.end(JSON.stringify({
1238
1288
  settings: {
@@ -1242,6 +1292,12 @@ export function registerAdminRoutes(deps) {
1242
1292
  ...(typeof authSettings.authRateLimitEnabled === 'boolean' ? { authRateLimitEnabled: authSettings.authRateLimitEnabled } : {}),
1243
1293
  ...(typeof authSettings.authRateLimitMaxRequests === 'number' ? { authRateLimitMaxRequests: authSettings.authRateLimitMaxRequests } : {}),
1244
1294
  ...(typeof authSettings.authRateLimitWindowMs === 'number' ? { authRateLimitWindowMs: authSettings.authRateLimitWindowMs } : {}),
1295
+ ...(typeof siteSettings.showRunTaskforceLocally === 'boolean' ? { showRunTaskforceLocally: siteSettings.showRunTaskforceLocally } : {}),
1296
+ ...(typeof siteSettings.pricingPageEnabled === 'boolean' ? { pricingPageEnabled: siteSettings.pricingPageEnabled } : {}),
1297
+ ...(typeof siteSettings.animatedBackgroundEnabled === 'boolean' ? { animatedBackgroundEnabled: siteSettings.animatedBackgroundEnabled } : {}),
1298
+ ...(siteSettings.siteTheme === 'light' || siteSettings.siteTheme === 'dark' ? { siteTheme: siteSettings.siteTheme } : {}),
1299
+ ...(typeof siteSettings.pricingPageTitle === 'string' ? { pricingPageTitle: siteSettings.pricingPageTitle } : {}),
1300
+ ...(typeof siteSettings.pricingPageDescription === 'string' ? { pricingPageDescription: siteSettings.pricingPageDescription } : {}),
1245
1301
  onboardingPolicyMode: onboardingPolicy.mode,
1246
1302
  onboardingPlanId: onboardingPolicy.planId
1247
1303
  },
@@ -1252,6 +1308,12 @@ export function registerAdminRoutes(deps) {
1252
1308
  authRateLimitEnabled: effective.authRateLimitEnabled,
1253
1309
  authRateLimitMaxRequests: effective.authRateLimitMaxRequests,
1254
1310
  authRateLimitWindowMs: effective.authRateLimitWindowMs,
1311
+ showRunTaskforceLocally: effectiveSite.showRunTaskforceLocally,
1312
+ pricingPageEnabled: effectiveSite.pricingPageEnabled,
1313
+ animatedBackgroundEnabled: effectiveSite.animatedBackgroundEnabled,
1314
+ siteTheme: effectiveSite.siteTheme,
1315
+ pricingPageTitle: effectiveSite.pricingPageTitle,
1316
+ pricingPageDescription: effectiveSite.pricingPageDescription,
1255
1317
  onboardingPolicyMode: effective.onboardingPolicyMode,
1256
1318
  onboardingPlanId: effective.onboardingPlanId
1257
1319
  }
@@ -1273,11 +1335,27 @@ export function registerAdminRoutes(deps) {
1273
1335
  : {}),
1274
1336
  ...(typeof siteSettings.pricingPageEnabled === 'boolean'
1275
1337
  ? { pricingPageEnabled: siteSettings.pricingPageEnabled }
1338
+ : {}),
1339
+ ...(typeof siteSettings.animatedBackgroundEnabled === 'boolean'
1340
+ ? { animatedBackgroundEnabled: siteSettings.animatedBackgroundEnabled }
1341
+ : {}),
1342
+ ...(siteSettings.siteTheme === 'light' || siteSettings.siteTheme === 'dark'
1343
+ ? { siteTheme: siteSettings.siteTheme }
1344
+ : {}),
1345
+ ...(typeof siteSettings.pricingPageTitle === 'string'
1346
+ ? { pricingPageTitle: siteSettings.pricingPageTitle }
1347
+ : {}),
1348
+ ...(typeof siteSettings.pricingPageDescription === 'string'
1349
+ ? { pricingPageDescription: siteSettings.pricingPageDescription }
1276
1350
  : {})
1277
1351
  },
1278
1352
  status: {
1279
1353
  showRunTaskforceLocally: siteBehavior.showRunTaskforceLocally,
1280
- pricingPageEnabled: siteBehavior.pricingPageEnabled
1354
+ pricingPageEnabled: siteBehavior.pricingPageEnabled,
1355
+ animatedBackgroundEnabled: siteBehavior.animatedBackgroundEnabled,
1356
+ siteTheme: siteBehavior.siteTheme,
1357
+ pricingPageTitle: siteBehavior.pricingPageTitle,
1358
+ pricingPageDescription: siteBehavior.pricingPageDescription
1281
1359
  }
1282
1360
  }));
1283
1361
  };
@@ -1448,9 +1526,11 @@ export function registerAdminRoutes(deps) {
1448
1526
  }
1449
1527
  const reqUrl = new URL(req.url || '/api/taskforce/admin/orphaned-assets', 'http://localhost');
1450
1528
  const workspaceId = String(reqUrl.searchParams.get('workspaceId') || requestWorkspaceId(req) || '').trim();
1529
+ const aiProfileAvatarStorageKeys = listReferencedAiProfileAvatarStorageKeys(workspaceId);
1451
1530
  const assets = workspaceAssetStore.listAllByWorkspace(workspaceId, {
1452
1531
  includeDeleted: false
1453
- }).filter((asset) => workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId).every((link) => !!link.deletedAt))
1532
+ }).filter((asset) => (!aiProfileAvatarStorageKeys.has(asset.storageKey)
1533
+ && workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId).every((link) => !!link.deletedAt)))
1454
1534
  .map((asset) => ({
1455
1535
  assetId: asset.assetId,
1456
1536
  workspaceId: asset.workspaceId,
@@ -1529,6 +1609,11 @@ export function registerAdminRoutes(deps) {
1529
1609
  res.end(JSON.stringify({ error: 'Asset not found.' }));
1530
1610
  return;
1531
1611
  }
1612
+ if (listReferencedAiProfileAvatarStorageKeys(workspaceId).has(asset.storageKey)) {
1613
+ res.writeHead(409, { 'Content-Type': 'application/json' });
1614
+ res.end(JSON.stringify({ error: 'Asset is still referenced by an AI profile avatar.' }));
1615
+ return;
1616
+ }
1532
1617
  const objectStorageClient = getObjectStorageClient();
1533
1618
  const { basePath } = core.getPaths();
1534
1619
  try {
@@ -1891,6 +1976,25 @@ export function registerAdminRoutes(deps) {
1891
1976
  : body?.onboardingPlanId === null
1892
1977
  ? null
1893
1978
  : currentOnboardingPolicy.planId;
1979
+ const pricingPageTitle = typeof body?.pricingPageTitle === 'string'
1980
+ ? body.pricingPageTitle.trim()
1981
+ : undefined;
1982
+ const pricingPageDescription = typeof body?.pricingPageDescription === 'string'
1983
+ ? body.pricingPageDescription.trim()
1984
+ : undefined;
1985
+ const siteTheme = body?.siteTheme === 'light' || body?.siteTheme === 'dark'
1986
+ ? body.siteTheme
1987
+ : undefined;
1988
+ if (pricingPageTitle !== undefined && pricingPageTitle.length > 120) {
1989
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1990
+ res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
1991
+ return;
1992
+ }
1993
+ if (pricingPageDescription !== undefined && pricingPageDescription.length > 280) {
1994
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1995
+ res.end(JSON.stringify({ error: 'Pricing page description must be 280 characters or fewer.', code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
1996
+ return;
1997
+ }
1894
1998
  const validation = core.validateOnboardingPolicy({
1895
1999
  mode: onboardingPolicyMode,
1896
2000
  planId: onboardingPlanId
@@ -1900,6 +2004,26 @@ export function registerAdminRoutes(deps) {
1900
2004
  res.end(JSON.stringify({ error: validation.message || 'Invalid onboarding policy.', code: validation.code, details: validation.details }));
1901
2005
  return;
1902
2006
  }
2007
+ const siteSettingsUpdate = {
2008
+ ...(typeof body?.showRunTaskforceLocally === 'boolean'
2009
+ ? { showRunTaskforceLocally: body.showRunTaskforceLocally }
2010
+ : {}),
2011
+ ...(typeof body?.pricingPageEnabled === 'boolean'
2012
+ ? { pricingPageEnabled: body.pricingPageEnabled }
2013
+ : {}),
2014
+ ...(typeof body?.animatedBackgroundEnabled === 'boolean'
2015
+ ? { animatedBackgroundEnabled: body.animatedBackgroundEnabled }
2016
+ : {}),
2017
+ ...(siteTheme !== undefined
2018
+ ? { siteTheme }
2019
+ : {}),
2020
+ ...(pricingPageTitle !== undefined
2021
+ ? { pricingPageTitle }
2022
+ : {}),
2023
+ ...(pricingPageDescription !== undefined
2024
+ ? { pricingPageDescription }
2025
+ : {})
2026
+ };
1903
2027
  core.updateGlobalSettings({
1904
2028
  auth: {
1905
2029
  ...(typeof body?.allowSelfRegistration === 'boolean' ? { allowSelfRegistration: body.allowSelfRegistration } : {}),
@@ -1916,7 +2040,10 @@ export function registerAdminRoutes(deps) {
1916
2040
  mode: onboardingPolicyMode,
1917
2041
  planId: onboardingPlanId ?? null
1918
2042
  }
1919
- }
2043
+ },
2044
+ ...(Object.keys(siteSettingsUpdate).length > 0
2045
+ ? { site: siteSettingsUpdate }
2046
+ : {})
1920
2047
  });
1921
2048
  res.writeHead(200, { 'Content-Type': 'application/json' });
1922
2049
  res.end(JSON.stringify({ success: true }));
@@ -1928,6 +2055,25 @@ export function registerAdminRoutes(deps) {
1928
2055
  return;
1929
2056
  }
1930
2057
  const body = await parseJsonBody(req);
2058
+ const pricingPageTitle = typeof body?.pricingPageTitle === 'string'
2059
+ ? body.pricingPageTitle.trim()
2060
+ : undefined;
2061
+ const pricingPageDescription = typeof body?.pricingPageDescription === 'string'
2062
+ ? body.pricingPageDescription.trim()
2063
+ : undefined;
2064
+ const siteTheme = body?.siteTheme === 'light' || body?.siteTheme === 'dark'
2065
+ ? body.siteTheme
2066
+ : undefined;
2067
+ if (pricingPageTitle !== undefined && pricingPageTitle.length > 120) {
2068
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2069
+ res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
2070
+ return;
2071
+ }
2072
+ if (pricingPageDescription !== undefined && pricingPageDescription.length > 280) {
2073
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2074
+ res.end(JSON.stringify({ error: 'Pricing page description must be 280 characters or fewer.', code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
2075
+ return;
2076
+ }
1931
2077
  core.updateGlobalSettings({
1932
2078
  site: {
1933
2079
  ...(typeof body?.showRunTaskforceLocally === 'boolean'
@@ -1935,6 +2081,18 @@ export function registerAdminRoutes(deps) {
1935
2081
  : {}),
1936
2082
  ...(typeof body?.pricingPageEnabled === 'boolean'
1937
2083
  ? { pricingPageEnabled: body.pricingPageEnabled }
2084
+ : {}),
2085
+ ...(typeof body?.animatedBackgroundEnabled === 'boolean'
2086
+ ? { animatedBackgroundEnabled: body.animatedBackgroundEnabled }
2087
+ : {}),
2088
+ ...(siteTheme !== undefined
2089
+ ? { siteTheme }
2090
+ : {}),
2091
+ ...(pricingPageTitle !== undefined
2092
+ ? { pricingPageTitle }
2093
+ : {}),
2094
+ ...(pricingPageDescription !== undefined
2095
+ ? { pricingPageDescription }
1938
2096
  : {})
1939
2097
  }
1940
2098
  });
@@ -2509,6 +2667,26 @@ export function registerAdminRoutes(deps) {
2509
2667
  res.end(JSON.stringify({ success: false, error: error.message, code: error?.code || 'MCP_TOKEN_REVOKE_FAILED' }));
2510
2668
  }
2511
2669
  });
2670
+ addRoute('DELETE', '/api/taskforce/settings/mcp/tokens/:id', async (req, res, params) => {
2671
+ if (!ensureAppAccess(req, res))
2672
+ return;
2673
+ const userId = resolveAuthenticatedUserId(req);
2674
+ if (!userId || userId === 'anonymous') {
2675
+ res.writeHead(401, { 'Content-Type': 'application/json' });
2676
+ res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
2677
+ return;
2678
+ }
2679
+ try {
2680
+ const deleted = core.deleteMcpAccessToken({ actorUserId: userId, workspaceId: requestWorkspaceId(req), tokenId: params.id });
2681
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2682
+ res.end(JSON.stringify({ success: true, deleted }));
2683
+ }
2684
+ catch (error) {
2685
+ const statusCode = resolveErrorStatusCode(error);
2686
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
2687
+ res.end(JSON.stringify({ success: false, error: error.message, code: error?.code || 'MCP_TOKEN_DELETE_FAILED' }));
2688
+ }
2689
+ });
2512
2690
  addRoute('POST', '/api/taskforce/settings/mcp/tokens/:id/regenerate', async (req, res, params) => {
2513
2691
  if (!ensureAppAccess(req, res))
2514
2692
  return;
@@ -40,6 +40,57 @@ export function registerAnnotatedAttachmentRoutes(deps) {
40
40
  });
41
41
  return false;
42
42
  };
43
+ addRoute('GET', '/api/taskforce/annotated-attachments/images', async (req, res) => {
44
+ const workspaceId = requestWorkspaceId(req);
45
+ if (!ensureAnnotatedAttachmentAccess(req, res, workspaceId))
46
+ return;
47
+ try {
48
+ const assetStore = core.getWorkspaceAssetStore();
49
+ const imageAssets = assetStore
50
+ .listAllByWorkspace(workspaceId, { kind: 'image' })
51
+ .filter((entry) => !entry.deletedAt);
52
+ const images = imageAssets.map((asset) => {
53
+ const activeLinks = assetStore
54
+ .listLinksForAsset(asset.assetId, workspaceId)
55
+ .filter((entry) => !entry.deletedAt)
56
+ .filter((entry) => entry.targetType !== 'task' || (!!entry.taskId && !!core.getTask(entry.taskId, workspaceId)));
57
+ const activeTaskLinks = activeLinks
58
+ .filter((entry) => entry.targetType === 'task' && !!entry.taskId)
59
+ .filter((entry) => !!core.getTask(entry.taskId, workspaceId));
60
+ const firstTaskLink = activeTaskLinks[0] || null;
61
+ const linkedTask = firstTaskLink?.taskId
62
+ ? core.getTask(firstTaskLink.taskId, workspaceId)
63
+ : null;
64
+ const sessions = core.listAnnotatedAttachmentSessions({
65
+ workspaceId,
66
+ imageAssetId: asset.assetId,
67
+ });
68
+ const displayName = String(asset.logicalName || asset.originalFilename || asset.assetId).trim() || asset.assetId;
69
+ return {
70
+ assetId: asset.assetId,
71
+ referenceNumber: typeof asset.referenceNumber === 'number' ? asset.referenceNumber : null,
72
+ imageReferenceLabel: getImageReferenceLabel(asset) || null,
73
+ path: `/api/taskforce/context/${encodeURIComponent(asset.storageKey)}`,
74
+ displayName,
75
+ originalFilename: asset.originalFilename || null,
76
+ mimeType: asset.mimeType,
77
+ sizeBytes: asset.sizeBytes,
78
+ updatedAt: asset.updatedAt || null,
79
+ attachmentCount: activeTaskLinks.length,
80
+ sessionCount: sessions.length,
81
+ taskId: firstTaskLink?.taskId || null,
82
+ taskReferenceLabel: linkedTask?.referenceLabel || null,
83
+ };
84
+ });
85
+ res.setHeader('Cache-Control', 'no-store');
86
+ writeJson(res, 200, { images });
87
+ }
88
+ catch (error) {
89
+ writeJson(res, resolveErrorStatusCode(error, 400), {
90
+ error: String(error?.message || 'Failed to list annotated attachment images.'),
91
+ });
92
+ }
93
+ });
43
94
  addRoute('GET', '/api/taskforce/annotated-attachments/images/:reference', async (req, res, params) => {
44
95
  const workspaceId = requestWorkspaceId(req);
45
96
  if (!ensureAnnotatedAttachmentAccess(req, res, workspaceId))
@@ -38,6 +38,8 @@
38
38
  * GET /api/taskforce/auth/workspace-members
39
39
  * GET /api/taskforce/workspace/assignee-options
40
40
  * POST /api/taskforce/workspace/ai-profiles/avatar
41
+ * POST /api/taskforce/workspace/ai-profiles/avatar/upload
42
+ * POST /api/taskforce/workspace/ai-profiles/surface-type
41
43
  * GET /api/taskforce/admin/users
42
44
  * POST /api/taskforce/admin/users/invite
43
45
  * PATCH /api/taskforce/admin/users/:id/role
@@ -52,6 +54,7 @@ import type { TaskforceCore } from '../../core/Taskforce.js';
52
54
  import { type RouteHandler, type RouteContext } from './primitives.js';
53
55
  import type { ResolvedObjectStorageConfig } from '../../storage/objectStorage.js';
54
56
  import { ObjectStorageClient } from '../../storage/objectStorageClient.js';
57
+ import type { WorkspaceAssetStore } from '../../storage/workspaceAssetStore.js';
55
58
  export interface AuthRouteDependencies {
56
59
  addRoute: (method: string, path: string, handler: RouteHandler) => void;
57
60
  core: TaskforceCore;
@@ -73,5 +76,13 @@ export interface AuthRouteDependencies {
73
76
  };
74
77
  resolveEffectiveObjectStorageConfig: () => ResolvedObjectStorageConfig;
75
78
  getObjectStorageClient: () => ObjectStorageClient | null;
79
+ workspaceAssetStore: WorkspaceAssetStore | null;
80
+ ensureWithinDir: (candidatePath: string, rootDir: string) => string | null;
81
+ markAssetDeleted: (pathValue: string) => unknown;
82
+ appendStorageTelemetry: (event: string, payload?: {
83
+ path?: string;
84
+ status?: string;
85
+ provider?: string;
86
+ }) => void;
76
87
  }
77
88
  export declare function registerAuthRoutes(deps: AuthRouteDependencies): void;