@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.
- package/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- 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
|
|
2204
|
-
const
|
|
2205
|
-
const
|
|
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 =
|
|
2208
|
-
|
|
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 =
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
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 -
|
|
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
|
|
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,10 @@ 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;
|
|
27
31
|
};
|
|
28
32
|
export declare function shouldExposeDevAuthTokens(): boolean;
|
|
29
33
|
export declare function resolveRuntimeConfigBaseUrl(req: IncomingMessage): string;
|
|
@@ -51,9 +51,20 @@ 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
|
|
57
68
|
};
|
|
58
69
|
}
|
|
59
70
|
export function shouldExposeDevAuthTokens() {
|
|
@@ -2553,6 +2553,15 @@ export function registerBillingRoutes(deps) {
|
|
|
2553
2553
|
const db = core.getDatabaseAdapter();
|
|
2554
2554
|
const tenantId = core.getTenantId();
|
|
2555
2555
|
const environment = requestPricingEnvironment(req);
|
|
2556
|
+
const siteSettings = typeof core.getGlobalSettings === 'function'
|
|
2557
|
+
? (core.getGlobalSettings().site || {})
|
|
2558
|
+
: {};
|
|
2559
|
+
const pageTitle = typeof siteSettings.pricingPageTitle === 'string'
|
|
2560
|
+
? (siteSettings.pricingPageTitle.trim() || 'Subscription Plans')
|
|
2561
|
+
: 'Subscription Plans';
|
|
2562
|
+
const pageDescription = typeof siteSettings.pricingPageDescription === 'string'
|
|
2563
|
+
? (siteSettings.pricingPageDescription.trim() || 'Manage your subscription and explore available features. Your current plan is highlighted below.')
|
|
2564
|
+
: 'Manage your subscription and explore available features. Your current plan is highlighted below.';
|
|
2556
2565
|
const cacheKey = `${tenantId}:${environment}`;
|
|
2557
2566
|
const cacheVersion = buildPublicPricingCacheVersion(core);
|
|
2558
2567
|
const cached = publicPricingCache.get(cacheKey);
|
|
@@ -2664,6 +2673,8 @@ export function registerBillingRoutes(deps) {
|
|
|
2664
2673
|
}).filter(Boolean);
|
|
2665
2674
|
const payload = {
|
|
2666
2675
|
asOf: new Date().toISOString(),
|
|
2676
|
+
pageTitle,
|
|
2677
|
+
pageDescription,
|
|
2667
2678
|
pricingSource: {
|
|
2668
2679
|
environment,
|
|
2669
2680
|
runtimeMode: 'cloud'
|
|
@@ -2733,6 +2733,8 @@ describe('Billing Webhook Routes', () => {
|
|
|
2733
2733
|
expect(res.statusCode).toBe(200);
|
|
2734
2734
|
const body = JSON.parse(res.body);
|
|
2735
2735
|
expect(body.plans).toBeInstanceOf(Array);
|
|
2736
|
+
expect(body.pageTitle).toBe('Subscription Plans');
|
|
2737
|
+
expect(body.pageDescription).toBe('Manage your subscription and explore available features. Your current plan is highlighted below.');
|
|
2736
2738
|
expect(body.pricingSource).toEqual({
|
|
2737
2739
|
environment: 'production',
|
|
2738
2740
|
runtimeMode: 'cloud'
|
|
@@ -2949,6 +2951,7 @@ describe('Billing Webhook Routes', () => {
|
|
|
2949
2951
|
await pricingRoute(req, res, {});
|
|
2950
2952
|
expect(res.statusCode).toBe(200);
|
|
2951
2953
|
const body = JSON.parse(res.body);
|
|
2954
|
+
expect(body.pageTitle).toBe('Subscription Plans');
|
|
2952
2955
|
expect(body.pricingSource).toEqual({
|
|
2953
2956
|
environment: 'performance',
|
|
2954
2957
|
runtimeMode: 'cloud'
|
|
@@ -474,6 +474,14 @@ export function registerDocumentRoutes(deps) {
|
|
|
474
474
|
if (shouldProxyInlineUserAvatar && await proxyCloudUserProfileAvatar(req, res, normalizedRelative, contentTypeForExt)) {
|
|
475
475
|
return;
|
|
476
476
|
}
|
|
477
|
+
if (shouldProxyInlineAiAvatar && filePath && fs.existsSync(filePath)) {
|
|
478
|
+
res.writeHead(200, {
|
|
479
|
+
'Content-Type': contentTypeForExt(ext),
|
|
480
|
+
'Content-Disposition': contentDisposition
|
|
481
|
+
});
|
|
482
|
+
res.end(fs.readFileSync(filePath));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
477
485
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
478
486
|
res.end('Context file not found');
|
|
479
487
|
return;
|
|
@@ -873,6 +881,7 @@ export function registerDocumentRoutes(deps) {
|
|
|
873
881
|
}));
|
|
874
882
|
return;
|
|
875
883
|
}
|
|
884
|
+
const attachedAt = new Date().toISOString();
|
|
876
885
|
const payload = buildTaskAttachmentPayload(asset.storageKey, {
|
|
877
886
|
caption: getCanonicalDocumentName({
|
|
878
887
|
assetId: asset.assetId,
|
|
@@ -883,6 +892,7 @@ export function registerDocumentRoutes(deps) {
|
|
|
883
892
|
}),
|
|
884
893
|
assetId: asset.assetId,
|
|
885
894
|
taskId: targetId,
|
|
895
|
+
timestamp: attachedAt,
|
|
886
896
|
});
|
|
887
897
|
if (!payload) {
|
|
888
898
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -216,16 +216,6 @@ export function makeResolveAuthenticatedMutationUserId(core, context, requestWor
|
|
|
216
216
|
const explicitUserId = String(requestUserId(req) || '').trim();
|
|
217
217
|
if (explicitUserId && explicitUserId !== 'anonymous')
|
|
218
218
|
return explicitUserId;
|
|
219
|
-
if (requestRuntimeMode(req) === 'local') {
|
|
220
|
-
const workspaceId = String(requestWorkspaceId(req) || '').trim() || 'default';
|
|
221
|
-
const fallbackUser = core.listWorkspaceUsers(workspaceId).find((user) => {
|
|
222
|
-
const status = String(user.status || '').trim().toLowerCase();
|
|
223
|
-
return status === 'active' && user.disabled !== true;
|
|
224
|
-
});
|
|
225
|
-
const fallbackUserId = String(fallbackUser?.userId || '').trim();
|
|
226
|
-
if (fallbackUserId)
|
|
227
|
-
return fallbackUserId;
|
|
228
|
-
}
|
|
229
219
|
throw new TaskforceRuleError('Authenticated user required for mutation attribution.', 401, 'MUTATION_AUTH_REQUIRED');
|
|
230
220
|
};
|
|
231
221
|
}
|