@taskforcehq/taskforce 0.3.306 → 0.3.308

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 (232) 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.d.ts +20 -15
  38. package/dist/components/views/PlanComparisonPage.js +221 -82
  39. package/dist/components/views/PlanComparisonPage.test.js +268 -14
  40. package/dist/components/views/PlansPage.js +16 -10
  41. package/dist/components/views/PlansPage.test.js +4 -2
  42. package/dist/components/views/StandaloneLayout.js +256 -69
  43. package/dist/components/views/WidgetView.js +10 -8
  44. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  45. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  46. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  47. package/dist/config/envSchema.js +1 -0
  48. package/dist/core/AiProfileService.d.ts +7 -0
  49. package/dist/core/AiProfileService.js +460 -35
  50. package/dist/core/AiProfiles.test.js +648 -2
  51. package/dist/core/AttachmentLinkService.d.ts +3 -0
  52. package/dist/core/AttachmentLinkService.js +58 -13
  53. package/dist/core/GlobalSettingsService.js +30 -0
  54. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  55. package/dist/core/GlobalSettingsService.test.js +45 -0
  56. package/dist/core/McpTokenService.d.ts +12 -0
  57. package/dist/core/McpTokenService.js +44 -1
  58. package/dist/core/PlanEntitlementService.d.ts +20 -0
  59. package/dist/core/PlanEntitlementService.js +215 -19
  60. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  61. package/dist/core/PlanningEntities.test.js +366 -4
  62. package/dist/core/TaskActivityService.d.ts +28 -1
  63. package/dist/core/TaskActivityService.js +227 -46
  64. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  65. package/dist/core/TaskActivitySync.test.js +90 -0
  66. package/dist/core/TaskAuditTimeline.test.js +96 -0
  67. package/dist/core/Taskforce.d.ts +204 -26
  68. package/dist/core/Taskforce.ids.test.js +58 -0
  69. package/dist/core/Taskforce.js +781 -71
  70. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  71. package/dist/core/types.d.ts +10 -0
  72. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  73. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  74. package/dist/hooks/bootstrapTimeouts.js +8 -0
  75. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  76. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  77. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  78. package/dist/hooks/sync/orchestratorShared.js +61 -1
  79. package/dist/hooks/sync/recovery.js +8 -0
  80. package/dist/hooks/sync/recovery.test.js +5 -5
  81. package/dist/hooks/sync/transfers.d.ts +2 -1
  82. package/dist/hooks/sync/transfers.js +36 -9
  83. package/dist/hooks/sync/transfers.test.js +534 -2
  84. package/dist/hooks/useFilterSort.js +13 -13
  85. package/dist/hooks/useFilterSort.test.js +87 -1
  86. package/dist/hooks/useSyncOrchestrator.js +50 -5
  87. package/dist/hooks/useTaskforce.d.ts +85 -50
  88. package/dist/hooks/useTaskforce.js +44 -14
  89. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  90. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  91. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  92. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  93. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  95. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  96. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  97. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  98. package/dist/mcp/clientIntegrations.d.ts +1 -0
  99. package/dist/mcp/clientIntegrations.js +25 -0
  100. package/dist/mcp/clientIntegrations.test.js +14 -3
  101. package/dist/mcp/clientProfileDefaults.js +3 -3
  102. package/dist/mcp/collaborationRegistrar.js +7 -7
  103. package/dist/mcp/documentAssetRegistrar.js +113 -7
  104. package/dist/mcp/runtime.d.ts +1 -0
  105. package/dist/mcp/runtime.js +969 -122
  106. package/dist/mcp/runtime.test.js +1083 -11
  107. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  108. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  109. package/dist/mcp/toolRegistry.d.ts +56 -0
  110. package/dist/mcp/toolRegistry.js +8 -0
  111. package/dist/mcp/toolRegistry.test.js +4 -0
  112. package/dist/migrations/taskSchemaMigrations.js +58 -15
  113. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  114. package/dist/server/aiProfileAvatarAssets.js +319 -0
  115. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  116. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  117. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  118. package/dist/server/index.js +4 -0
  119. package/dist/server/index.test.js +6 -1
  120. package/dist/server/routes/admin.js +328 -9
  121. package/dist/server/routes/annotatedAttachments.js +51 -0
  122. package/dist/server/routes/auth.d.ts +11 -0
  123. package/dist/server/routes/auth.js +189 -27
  124. package/dist/server/routes/authSupport.d.ts +5 -0
  125. package/dist/server/routes/authSupport.js +13 -1
  126. package/dist/server/routes/billing.d.ts +1 -0
  127. package/dist/server/routes/billing.js +116 -63
  128. package/dist/server/routes/billing.test.js +162 -11
  129. package/dist/server/routes/documents.js +10 -0
  130. package/dist/server/routes/shared.js +0 -10
  131. package/dist/server/routes/sync.integration.test.js +221 -0
  132. package/dist/server/routes/sync.js +13 -3
  133. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  134. package/dist/server/routes/tasks.js +12 -9
  135. package/dist/server/routes.js +87 -8
  136. package/dist/server/routes.test.js +972 -23
  137. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  138. package/dist/shared/aiProfileSurfaceType.js +10 -8
  139. package/dist/storage/objectStorage.d.ts +1 -0
  140. package/dist/storage/objectStorage.js +2 -1
  141. package/dist/storage/objectStorageClient.d.ts +1 -0
  142. package/dist/storage/objectStorageClient.js +41 -8
  143. package/dist/storage/objectStorageClient.test.js +26 -1
  144. package/dist/storage/objectStorageConfig.js +3 -1
  145. package/dist/storage/objectStorageConfig.test.js +13 -1
  146. package/dist/sync/collaborationSyncPayload.js +2 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -3
  149. package/dist/sync/collaborationSyncState.js +19 -12
  150. package/dist/sync/contentSyncPayload.d.ts +5 -1
  151. package/dist/sync/contentSyncPayload.js +9 -0
  152. package/dist/sync/contentSyncPayload.test.js +39 -0
  153. package/dist/sync/contentSyncState.d.ts +5 -1
  154. package/dist/sync/contentSyncState.js +231 -37
  155. package/dist/sync/planningSyncPayload.js +5 -0
  156. package/dist/sync/planningSyncPayload.test.js +24 -0
  157. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  158. package/dist/sync/syncApplyHandlers.js +89 -31
  159. package/dist/sync/syncApplyHandlers.test.js +184 -4
  160. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  161. package/dist/sync/syncResourceAdapters.js +2 -0
  162. package/dist/sync/syncService.d.ts +1 -0
  163. package/dist/sync/syncService.js +13 -1
  164. package/dist/sync/syncService.test.js +39 -0
  165. package/dist/sync/workspacePullFeed.d.ts +4 -0
  166. package/dist/sync/workspacePullFeed.js +129 -70
  167. package/dist/sync/workspacePullFeed.test.js +49 -0
  168. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  169. package/dist/sync/workspaceSyncModel.js +159 -45
  170. package/dist/sync/workspaceSyncModel.test.js +269 -3
  171. package/dist/types.d.ts +33 -2
  172. package/dist/ui/agent-logos/ChatGPT.png +0 -0
  173. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  174. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  175. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  176. package/dist/ui/agent-logos/codex.jpeg +0 -0
  177. package/dist/ui/agent-logos/cursor.png +0 -0
  178. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  179. package/dist/ui/agent-logos/windsurf.png +0 -0
  180. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
  181. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  182. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
  183. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
  184. package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
  185. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  186. package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
  187. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  188. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
  189. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  190. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  191. package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
  192. package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
  193. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
  194. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  195. package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
  196. package/dist/ui/assets/index-MAHKvFqp.js +4 -0
  197. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  198. package/dist/ui/index.html +3 -3
  199. package/dist/ui/og-image.png +0 -0
  200. package/dist/ui/product/agent manager.png +0 -0
  201. package/dist/ui/product/agent_manager_02.png +0 -0
  202. package/dist/utils/aiProfileEvents.d.ts +7 -0
  203. package/dist/utils/aiProfileEvents.js +6 -0
  204. package/dist/utils/assignees.d.ts +6 -0
  205. package/dist/utils/assignees.js +8 -1
  206. package/dist/utils/assignees.test.d.ts +1 -0
  207. package/dist/utils/assignees.test.js +25 -0
  208. package/dist/utils/avatarDisplay.d.ts +1 -0
  209. package/dist/utils/avatarDisplay.js +15 -0
  210. package/dist/utils/contextFiles.d.ts +3 -1
  211. package/dist/utils/contextFiles.js +2 -1
  212. package/dist/utils/taskActivity.d.ts +7 -1
  213. package/dist/utils/taskActivity.js +63 -1
  214. package/dist/utils/taskActivity.test.js +23 -1
  215. package/dist/utils/taskSearch.d.ts +22 -0
  216. package/dist/utils/taskSearch.js +87 -0
  217. package/dist/utils/taskSearch.test.d.ts +1 -0
  218. package/dist/utils/taskSearch.test.js +69 -0
  219. package/dist/utils/taskUtils.js +10 -3
  220. package/dist/utils/taskUtils.test.d.ts +1 -0
  221. package/dist/utils/taskUtils.test.js +19 -0
  222. package/package.json +1 -1
  223. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  224. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  225. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  226. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  227. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  228. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  229. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  230. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  231. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  232. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -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
@@ -61,9 +63,11 @@ import { resolveRuntimeModeContract } from '../../shared/runtimeContract.js';
61
63
  import { getRouteTimingNow, logSlowBootstrapRoute, requestBootstrapTraceId, requestHeader, resolveErrorStatusCode, serializeJsonWithTiming, setServerTimingHeader } from './shared.js';
62
64
  import { buildAuthFlowBehavior, buildSiteBehavior, getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl, shouldExposeDevAuthTokens } from './authSupport.js';
63
65
  import { normalizeTaskDataRelativePath } from '../../storage/objectStorageClient.js';
66
+ import { clearAiProfileAvatarAssets, saveAiProfileAvatarAssets } from '../aiProfileAvatarAssets.js';
67
+ const AI_PROFILE_AVATAR_UPLOAD_MAX_JSON_BYTES = 16 * 1024 * 1024;
64
68
  // === SECTION: Registration ===
65
69
  export function registerAuthRoutes(deps) {
66
- const { addRoute, core, context, requestWorkspaceId, requestUserId, resolveAuthenticatedUserId, ensureAppAccess, ensureAuthenticatedUser, ensureWorkspaceAdminRole, ensureCloudPlanData, requireCloudRuntime, auditAuthAction, resolveAdminRequestActor, resolveEffectiveObjectStorageConfig, getObjectStorageClient, } = deps;
70
+ const { addRoute, core, context, requestWorkspaceId, requestUserId, resolveAuthenticatedUserId, ensureAppAccess, ensureAuthenticatedUser, ensureWorkspaceAdminRole, ensureCloudPlanData, requireCloudRuntime, auditAuthAction, resolveAdminRequestActor, resolveEffectiveObjectStorageConfig, getObjectStorageClient, workspaceAssetStore, ensureWithinDir, markAssetDeleted, appendStorageTelemetry, } = deps;
67
71
  const normalizeTeamWorkspaceRole = (value) => {
68
72
  const role = String(value || '').trim().toLowerCase();
69
73
  if (role === 'owner' || role === 'admin' || role === 'member' || role === 'read-only')
@@ -76,6 +80,56 @@ export function registerAuthRoutes(deps) {
76
80
  const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
77
81
  return teamAllowed && stateAllows ? 'team' : 'personal';
78
82
  };
83
+ const serializeAiProfilePublicRecord = (profile) => ({
84
+ id: profile.id,
85
+ workspaceId: profile.workspaceId,
86
+ name: profile.name,
87
+ username: profile.username,
88
+ icon: profile.icon,
89
+ color: profile.color,
90
+ avatarUrl: profile.avatarUrl ?? null,
91
+ avatarSourceUrl: profile.avatarSourceUrl ?? null,
92
+ avatarRevision: profile.avatarRevision ?? 0,
93
+ avatarUpdatedAt: profile.avatarUpdatedAt ?? null,
94
+ description: profile.description ?? null,
95
+ role: profile.role ?? null,
96
+ provider: profile.provider ?? null,
97
+ model: profile.model ?? null,
98
+ surfaceType: profile.surfaceType ?? null,
99
+ seatScope: profile.seatScope ?? null,
100
+ archivedAt: profile.archivedAt ?? null,
101
+ archivedBy: profile.archivedBy ?? null,
102
+ archivedReason: profile.archivedReason ?? null,
103
+ mergedIntoProfileId: profile.mergedIntoProfileId ?? null,
104
+ rosterStateUpdatedAt: profile.rosterStateUpdatedAt ?? null,
105
+ createdAt: profile.createdAt,
106
+ updatedAt: profile.updatedAt,
107
+ lastActiveAt: profile.lastActiveAt ?? null
108
+ });
109
+ const parseAiAvatarDataUrlPayload = (value, fallbackOriginalName) => {
110
+ if (!value || typeof value !== 'object')
111
+ return null;
112
+ const record = value;
113
+ const dataUrl = String(record.dataUrl || record.file || '').trim();
114
+ const dataMatch = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
115
+ if (!dataMatch)
116
+ return null;
117
+ return {
118
+ mimeType: String(record.mimeType || dataMatch[1] || 'application/octet-stream').trim().toLowerCase(),
119
+ originalName: typeof record.originalName === 'string' && record.originalName.trim().length > 0
120
+ ? record.originalName.trim()
121
+ : fallbackOriginalName,
122
+ buffer: Buffer.from(dataMatch[2], 'base64')
123
+ };
124
+ };
125
+ const aiProfileAvatarAssetDeps = {
126
+ core,
127
+ workspaceAssetStore,
128
+ getObjectStorageClient,
129
+ ensureWithinDir,
130
+ markAssetDeleted,
131
+ appendStorageTelemetry,
132
+ };
79
133
  const resolveBillingConflictCodeForUser = (userIdRaw, gateRaw) => {
80
134
  const userId = String(userIdRaw || '').trim();
81
135
  const gate = String(gateRaw || '').trim().toLowerCase();
@@ -1062,6 +1116,8 @@ export function registerAuthRoutes(deps) {
1062
1116
  }
1063
1117
  const payload = {
1064
1118
  authenticated,
1119
+ authRequiredForApi: Boolean(authConfig?.enabled && authConfig.requiredForApi),
1120
+ runtimeMode: authConfig?.runtimeMode || 'local',
1065
1121
  userId,
1066
1122
  workspaceId,
1067
1123
  role,
@@ -2189,6 +2245,7 @@ export function registerAuthRoutes(deps) {
2189
2245
  res.end(JSON.stringify({ users }));
2190
2246
  });
2191
2247
  // GET /api/taskforce/workspace/assignee-options - Read active workspace humans and registered AI collaborators for task assignee selection.
2248
+ // Use ?kind=agent for Agent Manager roster loads that do not need human workspace members.
2192
2249
  addRoute('GET', '/api/taskforce/workspace/assignee-options', async (req, res) => {
2193
2250
  if (!ensureAppAccess(req, res))
2194
2251
  return;
@@ -2200,26 +2257,30 @@ export function registerAuthRoutes(deps) {
2200
2257
  const startedAt = getRouteTimingNow();
2201
2258
  const wid = requestWorkspaceId(req);
2202
2259
  const traceId = requestBootstrapTraceId(req);
2203
- const adminRoleStartedAt = getRouteTimingNow();
2204
- const includeProfileToken = context.authConfig?.enabled !== true || ensureWorkspaceAdminRole(req);
2205
- const adminRoleResolvedAt = getRouteTimingNow();
2260
+ const reqUrl = new URL(req.url || '/api/taskforce/workspace/assignee-options', 'http://localhost');
2261
+ const kindFilter = String(reqUrl.searchParams.get('kind') || '').trim().toLowerCase();
2262
+ const agentsOnly = kindFilter === 'agent' || kindFilter === 'agents' || kindFilter === 'ai-profile' || kindFilter === 'ai-profiles';
2206
2263
  const usersStartedAt = getRouteTimingNow();
2207
- const activeWorkspaceUsers = core.listWorkspaceUsers(wid)
2208
- .filter((user) => String(user.status || '').trim().toLowerCase() === 'active' && user.disabled !== true);
2264
+ const activeWorkspaceUsers = agentsOnly
2265
+ ? []
2266
+ : core.listWorkspaceUsers(wid)
2267
+ .filter((user) => String(user.status || '').trim().toLowerCase() === 'active' && user.disabled !== true);
2209
2268
  const usersLoadedAt = getRouteTimingNow();
2210
- const workspaceUsers = activeWorkspaceUsers.map((user) => ({
2211
- value: user.userId,
2212
- label: String(user.displayName || user.email || user.userId).trim() || user.userId,
2213
- icon: 'User',
2214
- color: '#22c55e',
2215
- kind: 'member',
2216
- userId: user.userId,
2217
- email: user.email,
2218
- displayName: user.displayName ?? null,
2219
- avatarUrl: user.avatarUrl ?? null,
2220
- status: user.status,
2221
- disabled: user.disabled === true
2222
- }));
2269
+ const workspaceUsers = agentsOnly
2270
+ ? []
2271
+ : activeWorkspaceUsers.map((user) => ({
2272
+ value: user.userId,
2273
+ label: String(user.displayName || user.email || user.userId).trim() || user.userId,
2274
+ icon: 'User',
2275
+ color: '#22c55e',
2276
+ kind: 'member',
2277
+ userId: user.userId,
2278
+ email: user.email,
2279
+ displayName: user.displayName ?? null,
2280
+ avatarUrl: user.avatarUrl ?? null,
2281
+ status: user.status,
2282
+ disabled: user.disabled === true
2283
+ }));
2223
2284
  const usersMappedAt = getRouteTimingNow();
2224
2285
  const aiProfileDiagnostics = { metadataBackfillCount: 0 };
2225
2286
  const aiProfilesStartedAt = getRouteTimingNow();
@@ -2228,12 +2289,13 @@ export function registerAuthRoutes(deps) {
2228
2289
  .map((profile) => ({
2229
2290
  value: profile.id,
2230
2291
  label: profile.name,
2231
- ...(includeProfileToken ? { profileToken: profile.profileToken } : {}),
2232
2292
  username: profile.username,
2233
2293
  icon: profile.icon,
2234
2294
  color: profile.color,
2235
2295
  avatarUrl: profile.avatarUrl ?? null,
2236
2296
  avatarSourceUrl: profile.avatarSourceUrl ?? null,
2297
+ avatarRevision: profile.avatarRevision ?? 0,
2298
+ avatarUpdatedAt: profile.avatarUpdatedAt ?? null,
2237
2299
  kind: 'agent',
2238
2300
  description: profile.description ?? null,
2239
2301
  role: profile.role ?? null,
@@ -2256,7 +2318,6 @@ export function registerAuthRoutes(deps) {
2256
2318
  const totalDurationMs = Math.max(0, getRouteTimingNow() - startedAt);
2257
2319
  setServerTimingHeader(res, [
2258
2320
  { name: 'assignee-options', durationMs: totalDurationMs },
2259
- { name: 'assignee-options-admin-role', durationMs: adminRoleResolvedAt - adminRoleStartedAt },
2260
2321
  { name: 'assignee-options-users', durationMs: usersLoadedAt - usersStartedAt },
2261
2322
  { name: 'assignee-options-users-map', durationMs: usersMappedAt - usersLoadedAt },
2262
2323
  { name: 'assignee-options-ai-profiles', durationMs: aiProfilesLoadedAt - aiProfilesStartedAt },
@@ -2272,9 +2333,9 @@ export function registerAuthRoutes(deps) {
2272
2333
  humanAssigneeCount: workspaceUsers.length,
2273
2334
  workspaceUserCount: activeWorkspaceUsers.length,
2274
2335
  aiProfileCount: aiProfiles.length,
2336
+ agentsOnly,
2275
2337
  metadataBackfillCount: aiProfileDiagnostics.metadataBackfillCount,
2276
2338
  responseBytes: serialized.byteLength,
2277
- adminRoleMs: Math.max(0, adminRoleResolvedAt - adminRoleStartedAt),
2278
2339
  listWorkspaceUsersMs: Math.max(0, usersLoadedAt - usersStartedAt),
2279
2340
  workspaceUsersMapMs: Math.max(0, usersMappedAt - usersLoadedAt),
2280
2341
  listAiProfilesMs: Math.max(0, aiProfilesLoadedAt - aiProfilesStartedAt),
@@ -2285,7 +2346,7 @@ export function registerAuthRoutes(deps) {
2285
2346
  res.writeHead(200, { 'Content-Type': 'application/json' });
2286
2347
  res.end(serialized.body);
2287
2348
  });
2288
- // POST /api/taskforce/workspace/ai-profiles/avatar - Assign or clear an AI profile avatar image.
2349
+ // POST /api/taskforce/workspace/ai-profiles/avatar - Clear an AI profile avatar image.
2289
2350
  addRoute('POST', '/api/taskforce/workspace/ai-profiles/avatar', async (req, res) => {
2290
2351
  if (!ensureWorkspaceAdminRole(req)) {
2291
2352
  res.writeHead(403, { 'Content-Type': 'application/json' });
@@ -2307,9 +2368,19 @@ export function registerAuthRoutes(deps) {
2307
2368
  return;
2308
2369
  }
2309
2370
  try {
2310
- const profile = core.updateAiProfileAvatar({ workspaceId: wid, profileId, avatarUrl, avatarSourceUrl });
2371
+ const shouldClearAvatar = avatarUrl === null && (avatarSourceUrl === null || avatarSourceUrl === undefined);
2372
+ if (!shouldClearAvatar) {
2373
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2374
+ res.end(JSON.stringify({
2375
+ error: 'AI profile avatar updates must use the stable avatar upload endpoint.',
2376
+ code: 'AI_PROFILE_AVATAR_STABLE_UPLOAD_REQUIRED'
2377
+ }));
2378
+ return;
2379
+ }
2380
+ const result = await clearAiProfileAvatarAssets(aiProfileAvatarAssetDeps, { workspaceId: wid, profileId });
2381
+ const profile = result.profile;
2311
2382
  res.writeHead(200, { 'Content-Type': 'application/json' });
2312
- res.end(JSON.stringify({ success: true, profile }));
2383
+ res.end(JSON.stringify({ success: true, profile: serializeAiProfilePublicRecord(profile) }));
2313
2384
  }
2314
2385
  catch (err) {
2315
2386
  const status = err?.statusCode ?? 500;
@@ -2317,6 +2388,58 @@ export function registerAuthRoutes(deps) {
2317
2388
  res.end(JSON.stringify({ error: err?.message ?? 'Failed to update AI profile avatar.' }));
2318
2389
  }
2319
2390
  });
2391
+ // POST /api/taskforce/workspace/ai-profiles/avatar/upload - Upload stable AI profile avatar slots.
2392
+ addRoute('POST', '/api/taskforce/workspace/ai-profiles/avatar/upload', async (req, res) => {
2393
+ if (!ensureWorkspaceAdminRole(req)) {
2394
+ res.writeHead(403, { 'Content-Type': 'application/json' });
2395
+ res.end(JSON.stringify({ error: 'Forbidden: owner or admin role required.' }));
2396
+ return;
2397
+ }
2398
+ const body = await parseJsonBody(req, AI_PROFILE_AVATAR_UPLOAD_MAX_JSON_BYTES);
2399
+ const wid = requestWorkspaceId(req);
2400
+ const profileId = String(body?.profileId || '').trim();
2401
+ if (!profileId) {
2402
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2403
+ res.end(JSON.stringify({ error: 'profileId is required.' }));
2404
+ return;
2405
+ }
2406
+ const display = parseAiAvatarDataUrlPayload(body?.displayImage || body?.display, 'display-avatar');
2407
+ if (!display) {
2408
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2409
+ res.end(JSON.stringify({ error: 'displayImage data URL is required.' }));
2410
+ return;
2411
+ }
2412
+ const source = parseAiAvatarDataUrlPayload(body?.sourceImage || body?.source, 'source-avatar');
2413
+ try {
2414
+ const result = await saveAiProfileAvatarAssets(aiProfileAvatarAssetDeps, {
2415
+ workspaceId: wid,
2416
+ profileId,
2417
+ display,
2418
+ source,
2419
+ actorId: resolveAuthenticatedUserId(req)
2420
+ });
2421
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2422
+ res.end(JSON.stringify({
2423
+ success: true,
2424
+ profile: serializeAiProfilePublicRecord(result.profile),
2425
+ assets: {
2426
+ display: {
2427
+ assetId: result.displayAsset.assetId,
2428
+ storageKey: result.displayStorageKey,
2429
+ },
2430
+ source: {
2431
+ assetId: result.sourceAsset.assetId,
2432
+ storageKey: result.sourceStorageKey,
2433
+ }
2434
+ }
2435
+ }));
2436
+ }
2437
+ catch (err) {
2438
+ const status = err?.statusCode ?? 500;
2439
+ res.writeHead(status, { 'Content-Type': 'application/json' });
2440
+ res.end(JSON.stringify({ error: err?.message ?? 'Failed to upload AI profile avatar.' }));
2441
+ }
2442
+ });
2320
2443
  // POST /api/taskforce/workspace/ai-profiles/merge - Merge a duplicate AI profile into another, reassigning all references.
2321
2444
  addRoute('POST', '/api/taskforce/workspace/ai-profiles/merge', async (req, res) => {
2322
2445
  if (!ensureWorkspaceAdminRole(req)) {
@@ -2336,7 +2459,7 @@ export function registerAuthRoutes(deps) {
2336
2459
  try {
2337
2460
  const profile = core.mergeAiProfiles({ workspaceId: wid, keepId, mergeId });
2338
2461
  res.writeHead(200, { 'Content-Type': 'application/json' });
2339
- res.end(JSON.stringify({ profile }));
2462
+ res.end(JSON.stringify({ profile: serializeAiProfilePublicRecord(profile) }));
2340
2463
  }
2341
2464
  catch (err) {
2342
2465
  const status = err?.statusCode ?? 500;
@@ -2344,6 +2467,45 @@ export function registerAuthRoutes(deps) {
2344
2467
  res.end(JSON.stringify({ error: err?.message ?? 'Merge failed.' }));
2345
2468
  }
2346
2469
  });
2470
+ // POST /api/taskforce/workspace/ai-profiles/surface-type - Manually re-categorize an active AI profile.
2471
+ addRoute('POST', '/api/taskforce/workspace/ai-profiles/surface-type', async (req, res) => {
2472
+ if (!ensureWorkspaceAdminRole(req)) {
2473
+ res.writeHead(403, { 'Content-Type': 'application/json' });
2474
+ res.end(JSON.stringify({ error: 'Forbidden: owner or admin role required.' }));
2475
+ return;
2476
+ }
2477
+ const body = await parseJsonBody(req);
2478
+ const wid = requestWorkspaceId(req);
2479
+ const profileId = String(body.profileId || '').trim();
2480
+ const hasSurfaceType = Boolean(body && Object.prototype.hasOwnProperty.call(body, 'surfaceType'));
2481
+ if (!profileId) {
2482
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2483
+ res.end(JSON.stringify({ error: 'profileId is required.' }));
2484
+ return;
2485
+ }
2486
+ if (!hasSurfaceType) {
2487
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2488
+ res.end(JSON.stringify({ error: 'surfaceType is required.' }));
2489
+ return;
2490
+ }
2491
+ try {
2492
+ const profile = core.updateAiProfileSurfaceType({
2493
+ workspaceId: wid,
2494
+ profileId,
2495
+ surfaceType: body.surfaceType
2496
+ });
2497
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2498
+ res.end(JSON.stringify({ success: true, profile: serializeAiProfilePublicRecord(profile) }));
2499
+ }
2500
+ catch (err) {
2501
+ const status = err?.statusCode ?? 500;
2502
+ res.writeHead(status, { 'Content-Type': 'application/json' });
2503
+ res.end(JSON.stringify({
2504
+ error: err?.message ?? 'Failed to update AI profile category.',
2505
+ code: err?.code
2506
+ }));
2507
+ }
2508
+ });
2347
2509
  // POST /api/taskforce/workspace/ai-profiles/archive - Remove an AI profile from the active roster without deleting its history.
2348
2510
  addRoute('POST', '/api/taskforce/workspace/ai-profiles/archive', async (req, res) => {
2349
2511
  if (!ensureWorkspaceAdminRole(req)) {
@@ -2368,7 +2530,7 @@ export function registerAuthRoutes(deps) {
2368
2530
  reason
2369
2531
  });
2370
2532
  res.writeHead(200, { 'Content-Type': 'application/json' });
2371
- res.end(JSON.stringify({ profile }));
2533
+ res.end(JSON.stringify({ profile: serializeAiProfilePublicRecord(profile) }));
2372
2534
  }
2373
2535
  catch (err) {
2374
2536
  const status = err?.statusCode ?? 500;
@@ -24,6 +24,11 @@ export declare function buildAuthBehavior(core: TaskforceCore): {
24
24
  export declare function buildSiteBehavior(core: TaskforceCore): {
25
25
  showRunTaskforceLocally: boolean;
26
26
  pricingPageEnabled: boolean;
27
+ animatedBackgroundEnabled: boolean;
28
+ siteTheme: string;
29
+ pricingPageTitle: string;
30
+ pricingPageDescription: string;
31
+ pricingPageShowPublicDescriptions: boolean;
27
32
  };
28
33
  export declare function shouldExposeDevAuthTokens(): boolean;
29
34
  export declare function resolveRuntimeConfigBaseUrl(req: IncomingMessage): string;
@@ -51,9 +51,21 @@ export function buildAuthBehavior(core) {
51
51
  export function buildSiteBehavior(core) {
52
52
  const globalSettings = core.getGlobalSettings();
53
53
  const siteSettings = globalSettings.site || {};
54
+ const pricingPageTitle = typeof siteSettings.pricingPageTitle === 'string'
55
+ ? (siteSettings.pricingPageTitle.trim() || 'Subscription Plans')
56
+ : 'Subscription Plans';
57
+ const pricingPageDescription = typeof siteSettings.pricingPageDescription === 'string'
58
+ ? (siteSettings.pricingPageDescription.trim() || 'Manage your subscription and explore available features. Your current plan is highlighted below.')
59
+ : 'Manage your subscription and explore available features. Your current plan is highlighted below.';
60
+ const siteTheme = siteSettings.siteTheme === 'light' ? 'light' : 'dark';
54
61
  return {
55
62
  showRunTaskforceLocally: resolveBooleanSetting(siteSettings.showRunTaskforceLocally, true),
56
- pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
63
+ pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true),
64
+ animatedBackgroundEnabled: resolveBooleanSetting(siteSettings.animatedBackgroundEnabled, false),
65
+ siteTheme,
66
+ pricingPageTitle,
67
+ pricingPageDescription,
68
+ pricingPageShowPublicDescriptions: resolveBooleanSetting(siteSettings.pricingPageShowPublicDescriptions, false)
57
69
  };
58
70
  }
59
71
  export function shouldExposeDevAuthTokens() {
@@ -3,6 +3,7 @@ import type { TaskforceCore } from '../../core/Taskforce.js';
3
3
  import { type RouteHandler, type RouteContext } from './primitives.js';
4
4
  export type BillingInterval = 'month' | 'year';
5
5
  export type BillingPricingType = 'stripe' | 'free';
6
+ export type BillingPricingAudience = 'public' | 'campaign';
6
7
  export interface BillingRouteDependencies {
7
8
  addRoute: (method: string, path: string, handler: RouteHandler) => void;
8
9
  core: TaskforceCore;