@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.
- 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.d.ts +20 -15
- package/dist/components/views/PlanComparisonPage.js +221 -82
- package/dist/components/views/PlanComparisonPage.test.js +268 -14
- package/dist/components/views/PlansPage.js +16 -10
- package/dist/components/views/PlansPage.test.js +4 -2
- 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 +30 -0
- package/dist/core/GlobalSettingsService.test.d.ts +1 -0
- package/dist/core/GlobalSettingsService.test.js +45 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +20 -0
- package/dist/core/PlanEntitlementService.js +215 -19
- package/dist/core/PlanFeatureCatalog.test.js +55 -0
- 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 +204 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +781 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +10 -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 +58 -15
- 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 +328 -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 +5 -0
- package/dist/server/routes/authSupport.js +13 -1
- package/dist/server/routes/billing.d.ts +1 -0
- package/dist/server/routes/billing.js +116 -63
- package/dist/server/routes/billing.test.js +162 -11
- 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 +972 -23
- 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/agent-logos/ChatGPT.png +0 -0
- package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
- package/dist/ui/agent-logos/antigravity.jpeg +0 -0
- package/dist/ui/agent-logos/claude-code.jpeg +0 -0
- package/dist/ui/agent-logos/codex.jpeg +0 -0
- package/dist/ui/agent-logos/cursor.png +0 -0
- package/dist/ui/agent-logos/openclaw.jpeg +0 -0
- package/dist/ui/agent-logos/windsurf.png +0 -0
- package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
- package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
- package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
- package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
- package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
- package/dist/ui/assets/index-MAHKvFqp.js +4 -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/og-image.png +0 -0
- 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
|
@@ -72,6 +72,7 @@ import { buildCloudMcpRuntimeAuth, resolveErrorStatusCode } from './shared.js';
|
|
|
72
72
|
import { buildAuthBehavior, buildSiteBehavior, getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl, } from './authSupport.js';
|
|
73
73
|
import { applyWorkspaceRepair, scanWorkspaceRepair } from '../../sync/workspaceRepair.js';
|
|
74
74
|
import { testMintAppleClientSecret } from '../auth/providers/appleClientSecret.js';
|
|
75
|
+
const PRICING_PAGE_DESCRIPTION_MAX_LENGTH = 2000;
|
|
75
76
|
// === SECTION: Registration ===
|
|
76
77
|
export function registerAdminRoutes(deps) {
|
|
77
78
|
const { addRoute, core, context, requestWorkspaceId, requestUserId, requestRole, requestHeader, requestRuntimeMode, ensureAppAccess, ensureAdminRole, ensureCloudPlanData, requireCloudRuntime, resolveAuthenticatedUserId, resolveAdminRequestActor, auditAdminAction, resolveEffectiveObjectStorageConfig, getObjectStorageClient, taskAssetStore, normalizeDocumentPathInput, ensureWithinDir, getAssetMetadata, markAssetDeleted, clearAssetDeleted, removeAssetMetadata, getStorageTelemetry, appendStorageTelemetry, getStorageIntegrityHistory, appendStorageIntegrityHistory, getDocumentPurgeHistory, appendDocumentPurgeHistory, getCollaborationTelemetry, isCollaborationPolicyEnabled, normalizeMcpOauthScopeString, MCP_TOKEN_SCOPE_VALUES, MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS, MCP_WRITE_RATE_LIMIT_ENABLED, MCP_WRITE_RATE_LIMIT_ACTOR_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WORKSPACE_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WINDOW_MS, } = deps;
|
|
@@ -79,6 +80,26 @@ export function registerAdminRoutes(deps) {
|
|
|
79
80
|
const workspaceAssetStore = typeof core.getDatabaseAdapter === 'function' && typeof core.getTenantId === 'function'
|
|
80
81
|
? new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId())
|
|
81
82
|
: null;
|
|
83
|
+
const isAiProfileAvatarStorageKey = (storageKey, workspaceId) => {
|
|
84
|
+
const normalized = String(storageKey || '').replace(/\\/g, '/');
|
|
85
|
+
return normalized.startsWith(`workspaces/${workspaceId}/assets/images/ai-profiles/`)
|
|
86
|
+
&& normalized.includes('/avatar/');
|
|
87
|
+
};
|
|
88
|
+
const listReferencedAiProfileAvatarStorageKeys = (workspaceId) => {
|
|
89
|
+
const referenced = new Set();
|
|
90
|
+
const profiles = typeof core.listAiProfiles === 'function'
|
|
91
|
+
? core.listAiProfiles(workspaceId, { includeArchived: true })
|
|
92
|
+
: [];
|
|
93
|
+
for (const profile of Array.isArray(profiles) ? profiles : []) {
|
|
94
|
+
for (const value of [profile?.avatarUrl, profile?.avatarSourceUrl]) {
|
|
95
|
+
const storageKey = normalizeDocumentPathInput(value);
|
|
96
|
+
if (!storageKey || !isAiProfileAvatarStorageKey(storageKey, workspaceId))
|
|
97
|
+
continue;
|
|
98
|
+
referenced.add(storageKey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return referenced;
|
|
102
|
+
};
|
|
82
103
|
// === SECTION: Email helpers ===
|
|
83
104
|
const resolveEffectiveVirusScanSettings = () => {
|
|
84
105
|
const settings = core.getGlobalSettings().objectStorage || {};
|
|
@@ -95,11 +116,15 @@ export function registerAdminRoutes(deps) {
|
|
|
95
116
|
};
|
|
96
117
|
// === SECTION: MCP helpers ===
|
|
97
118
|
const MCP_CONNECTION_TEST_TIMEOUT_MS = 5000;
|
|
98
|
-
const writeMcpHttpError = (res, statusCode, message) => {
|
|
119
|
+
const writeMcpHttpError = (res, statusCode, message, data) => {
|
|
99
120
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
100
121
|
res.end(JSON.stringify({
|
|
101
122
|
jsonrpc: '2.0',
|
|
102
|
-
error: {
|
|
123
|
+
error: {
|
|
124
|
+
code: -32000,
|
|
125
|
+
message,
|
|
126
|
+
...(data ? { data } : {}),
|
|
127
|
+
},
|
|
103
128
|
id: null,
|
|
104
129
|
}));
|
|
105
130
|
};
|
|
@@ -394,6 +419,8 @@ export function registerAdminRoutes(deps) {
|
|
|
394
419
|
authenticatedViaOAuth: oauthAuth || null,
|
|
395
420
|
userId: null,
|
|
396
421
|
...bootstrap,
|
|
422
|
+
aiProfileToken: bootstrap.aiProfileToken || oauthAuth?.aiProfileToken || null,
|
|
423
|
+
aiProfileName: bootstrap.aiProfileName || oauthAuth?.aiProfileName || null,
|
|
397
424
|
};
|
|
398
425
|
}
|
|
399
426
|
core.addMcpAuditLog({
|
|
@@ -506,6 +533,7 @@ export function registerAdminRoutes(deps) {
|
|
|
506
533
|
return;
|
|
507
534
|
}
|
|
508
535
|
const authScopes = auth.authenticatedViaToken?.scopes || auth.authenticatedViaOAuth?.scopes || [];
|
|
536
|
+
const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID();
|
|
509
537
|
const deniedToolName = findDeniedCloudMcpToolName(body, authScopes);
|
|
510
538
|
if (deniedToolName) {
|
|
511
539
|
core.addMcpAuditLog({
|
|
@@ -515,7 +543,7 @@ export function registerAdminRoutes(deps) {
|
|
|
515
543
|
tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
|
|
516
544
|
toolName: deniedToolName,
|
|
517
545
|
details: {
|
|
518
|
-
requestId
|
|
546
|
+
requestId,
|
|
519
547
|
ip: req.socket?.remoteAddress || null,
|
|
520
548
|
userAgent: requestHeader(req, 'user-agent', '') || null,
|
|
521
549
|
outcome: 'deny',
|
|
@@ -528,11 +556,13 @@ export function registerAdminRoutes(deps) {
|
|
|
528
556
|
}
|
|
529
557
|
if (!applyCloudMcpWriteRateLimit(req, res, auth, body))
|
|
530
558
|
return;
|
|
559
|
+
let runtime = null;
|
|
531
560
|
try {
|
|
532
|
-
|
|
561
|
+
runtime = createTaskforceMcpServer({
|
|
533
562
|
projectRoot: core.getPaths().projectRoot,
|
|
534
563
|
tenantId: core.getTenantId(),
|
|
535
564
|
workspaceId: auth.workspaceId,
|
|
565
|
+
core,
|
|
536
566
|
serverName: 'taskforce-cloud',
|
|
537
567
|
logStartup: false,
|
|
538
568
|
runtimeMode: 'cloud',
|
|
@@ -540,7 +570,7 @@ export function registerAdminRoutes(deps) {
|
|
|
540
570
|
req,
|
|
541
571
|
auth,
|
|
542
572
|
requestHeader,
|
|
543
|
-
requestId
|
|
573
|
+
requestId,
|
|
544
574
|
authScopes,
|
|
545
575
|
}),
|
|
546
576
|
});
|
|
@@ -557,8 +587,27 @@ export function registerAdminRoutes(deps) {
|
|
|
557
587
|
recordCloudMcpCredentialUsage(req, auth.authenticatedViaToken, auth.authenticatedViaOAuth);
|
|
558
588
|
}
|
|
559
589
|
catch (error) {
|
|
590
|
+
console.error('[Taskforce MCP] Cloud transport request failed:', {
|
|
591
|
+
requestId,
|
|
592
|
+
kind: 'tool_transport_failure',
|
|
593
|
+
message: error instanceof Error ? error.message : String(error),
|
|
594
|
+
});
|
|
560
595
|
if (!res.headersSent) {
|
|
561
|
-
writeMcpHttpError(res, 500, '
|
|
596
|
+
writeMcpHttpError(res, 500, 'MCP transport failure.', {
|
|
597
|
+
kind: 'tool_transport_failure',
|
|
598
|
+
retryable: true,
|
|
599
|
+
requestId,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
if (runtime) {
|
|
605
|
+
try {
|
|
606
|
+
await runtime.server.close();
|
|
607
|
+
}
|
|
608
|
+
catch (closeError) {
|
|
609
|
+
console.warn('[Taskforce MCP] Cloud transport cleanup failed:', closeError);
|
|
610
|
+
}
|
|
562
611
|
}
|
|
563
612
|
}
|
|
564
613
|
};
|
|
@@ -664,6 +713,88 @@ export function registerAdminRoutes(deps) {
|
|
|
664
713
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to list feature catalog'), code: error?.code }));
|
|
665
714
|
}
|
|
666
715
|
});
|
|
716
|
+
addRoute('POST', '/api/platform/features', async (req, res) => {
|
|
717
|
+
if (!ensureCloudPlanData(req, res))
|
|
718
|
+
return;
|
|
719
|
+
if (!ensureAdminRole(req)) {
|
|
720
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
721
|
+
res.end(JSON.stringify({ error: 'Forbidden: system admin role required.' }));
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const body = await parseJsonBody(req);
|
|
725
|
+
const label = String(body?.label || '');
|
|
726
|
+
const description = body?.description === undefined || body?.description === null
|
|
727
|
+
? body?.description
|
|
728
|
+
: String(body.description);
|
|
729
|
+
const publicLabel = body?.publicLabel === undefined || body?.publicLabel === null
|
|
730
|
+
? body?.publicLabel
|
|
731
|
+
: String(body.publicLabel);
|
|
732
|
+
const publicDescription = body?.publicDescription === undefined || body?.publicDescription === null
|
|
733
|
+
? body?.publicDescription
|
|
734
|
+
: String(body.publicDescription);
|
|
735
|
+
const publicDescriptionVisible = body?.publicDescriptionVisible === undefined || body?.publicDescriptionVisible === null
|
|
736
|
+
? body?.publicDescriptionVisible
|
|
737
|
+
: (body.publicDescriptionVisible === false ? false : true);
|
|
738
|
+
const publicDisplayOrder = body?.publicDisplayOrder === undefined || body?.publicDisplayOrder === null || body?.publicDisplayOrder === ''
|
|
739
|
+
? null
|
|
740
|
+
: Math.max(0, Math.floor(Number(body.publicDisplayOrder)));
|
|
741
|
+
if (!label.trim()) {
|
|
742
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
743
|
+
res.end(JSON.stringify({ success: false, error: 'Feature label is required.', code: 'PLAN_FEATURE_LABEL_REQUIRED' }));
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (label.trim().length > 120) {
|
|
747
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
748
|
+
res.end(JSON.stringify({ success: false, error: 'Feature label must be 120 characters or fewer.', code: 'PLAN_FEATURE_LABEL_TOO_LONG' }));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (typeof description === 'string' && description.trim().length > 280) {
|
|
752
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
753
|
+
res.end(JSON.stringify({ success: false, error: 'Feature description must be 280 characters or fewer.', code: 'PLAN_FEATURE_DESCRIPTION_TOO_LONG' }));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (typeof publicLabel === 'string' && publicLabel.trim().length > 120) {
|
|
757
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
758
|
+
res.end(JSON.stringify({ success: false, error: 'Public label must be 120 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_LABEL_TOO_LONG' }));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (typeof publicDescription === 'string' && publicDescription.trim().length > 280) {
|
|
762
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
763
|
+
res.end(JSON.stringify({ success: false, error: 'Public description must be 280 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_DESCRIPTION_TOO_LONG' }));
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (publicDisplayOrder !== null && !Number.isFinite(publicDisplayOrder)) {
|
|
767
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
768
|
+
res.end(JSON.stringify({ success: false, error: 'Public display order must be a non-negative number.', code: 'PLAN_FEATURE_PUBLIC_DISPLAY_ORDER_INVALID' }));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const feature = core.createCustomPlanFeatureCatalogEntry({
|
|
773
|
+
label,
|
|
774
|
+
description,
|
|
775
|
+
publicLabel,
|
|
776
|
+
publicDescription,
|
|
777
|
+
publicDescriptionVisible,
|
|
778
|
+
publicDisplayOrder
|
|
779
|
+
});
|
|
780
|
+
auditAdminAction(req, 'platform-plan-feature-catalog-created', {
|
|
781
|
+
featureKey: feature.featureKey,
|
|
782
|
+
label: feature.label,
|
|
783
|
+
description: feature.description,
|
|
784
|
+
publicLabel: feature.publicLabel ?? null,
|
|
785
|
+
publicDescription: feature.publicDescription ?? null,
|
|
786
|
+
publicDescriptionVisible: feature.publicDescriptionVisible ?? null,
|
|
787
|
+
publicDisplayOrder: feature.publicDisplayOrder ?? null
|
|
788
|
+
});
|
|
789
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
790
|
+
res.end(JSON.stringify({ success: true, feature }));
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
794
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
795
|
+
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to create feature catalog entry'), code: error?.code }));
|
|
796
|
+
}
|
|
797
|
+
});
|
|
667
798
|
addRoute('PATCH', '/api/platform/features/:featureKey', async (req, res, params) => {
|
|
668
799
|
if (!ensureCloudPlanData(req, res))
|
|
669
800
|
return;
|
|
@@ -674,15 +805,34 @@ export function registerAdminRoutes(deps) {
|
|
|
674
805
|
}
|
|
675
806
|
const body = await parseJsonBody(req);
|
|
676
807
|
const featureKey = String(params.featureKey || '').trim();
|
|
808
|
+
const label = body?.label === undefined || body?.label === null
|
|
809
|
+
? body?.label
|
|
810
|
+
: String(body.label);
|
|
811
|
+
const description = body?.description === undefined || body?.description === null
|
|
812
|
+
? body?.description
|
|
813
|
+
: String(body.description);
|
|
677
814
|
const publicLabel = body?.publicLabel === undefined || body?.publicLabel === null
|
|
678
815
|
? body?.publicLabel
|
|
679
816
|
: String(body.publicLabel);
|
|
680
817
|
const publicDescription = body?.publicDescription === undefined || body?.publicDescription === null
|
|
681
818
|
? body?.publicDescription
|
|
682
819
|
: String(body.publicDescription);
|
|
820
|
+
const publicDescriptionVisible = body?.publicDescriptionVisible === undefined || body?.publicDescriptionVisible === null
|
|
821
|
+
? body?.publicDescriptionVisible
|
|
822
|
+
: (body.publicDescriptionVisible === false ? false : true);
|
|
683
823
|
const publicDisplayOrder = body?.publicDisplayOrder === undefined || body?.publicDisplayOrder === null || body?.publicDisplayOrder === ''
|
|
684
824
|
? null
|
|
685
825
|
: Math.max(0, Math.floor(Number(body.publicDisplayOrder)));
|
|
826
|
+
if (typeof label === 'string' && label.trim().length > 120) {
|
|
827
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
828
|
+
res.end(JSON.stringify({ success: false, error: 'Feature label must be 120 characters or fewer.', code: 'PLAN_FEATURE_LABEL_TOO_LONG' }));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (typeof description === 'string' && description.trim().length > 280) {
|
|
832
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
833
|
+
res.end(JSON.stringify({ success: false, error: 'Feature description must be 280 characters or fewer.', code: 'PLAN_FEATURE_DESCRIPTION_TOO_LONG' }));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
686
836
|
if (typeof publicLabel === 'string' && publicLabel.trim().length > 120) {
|
|
687
837
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
688
838
|
res.end(JSON.stringify({ success: false, error: 'Public label must be 120 characters or fewer.', code: 'PLAN_FEATURE_PUBLIC_LABEL_TOO_LONG' }));
|
|
@@ -701,14 +851,20 @@ export function registerAdminRoutes(deps) {
|
|
|
701
851
|
try {
|
|
702
852
|
const feature = core.updatePlanFeatureCatalogMarketingCopy({
|
|
703
853
|
featureKey,
|
|
854
|
+
label,
|
|
855
|
+
description,
|
|
704
856
|
publicLabel,
|
|
705
857
|
publicDescription,
|
|
858
|
+
publicDescriptionVisible,
|
|
706
859
|
publicDisplayOrder
|
|
707
860
|
});
|
|
708
861
|
auditAdminAction(req, 'platform-plan-feature-catalog-updated', {
|
|
709
862
|
featureKey,
|
|
863
|
+
label: feature.label,
|
|
864
|
+
description: feature.description,
|
|
710
865
|
publicLabel: feature.publicLabel ?? null,
|
|
711
866
|
publicDescription: feature.publicDescription ?? null,
|
|
867
|
+
publicDescriptionVisible: feature.publicDescriptionVisible ?? null,
|
|
712
868
|
publicDisplayOrder: feature.publicDisplayOrder ?? null
|
|
713
869
|
});
|
|
714
870
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -720,6 +876,27 @@ export function registerAdminRoutes(deps) {
|
|
|
720
876
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to update feature catalog entry'), code: error?.code }));
|
|
721
877
|
}
|
|
722
878
|
});
|
|
879
|
+
addRoute('DELETE', '/api/platform/features/:featureKey', async (req, res, params) => {
|
|
880
|
+
if (!ensureCloudPlanData(req, res))
|
|
881
|
+
return;
|
|
882
|
+
if (!ensureAdminRole(req)) {
|
|
883
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
884
|
+
res.end(JSON.stringify({ error: 'Forbidden: system admin role required.' }));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const featureKey = String(params.featureKey || '').trim();
|
|
888
|
+
try {
|
|
889
|
+
core.deleteCustomPlanFeatureCatalogEntry(featureKey);
|
|
890
|
+
auditAdminAction(req, 'platform-plan-feature-catalog-deleted', { featureKey });
|
|
891
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
892
|
+
res.end(JSON.stringify({ success: true }));
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
896
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
897
|
+
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Unable to delete feature catalog entry'), code: error?.code }));
|
|
898
|
+
}
|
|
899
|
+
});
|
|
723
900
|
addRoute('POST', '/api/platform/plans/:planId/versions', async (req, res, params) => {
|
|
724
901
|
if (!ensureCloudPlanData(req, res))
|
|
725
902
|
return;
|
|
@@ -1231,8 +1408,10 @@ export function registerAdminRoutes(deps) {
|
|
|
1231
1408
|
}
|
|
1232
1409
|
const globalSettings = core.getGlobalSettings();
|
|
1233
1410
|
const authSettings = globalSettings.auth || {};
|
|
1411
|
+
const siteSettings = globalSettings.site || {};
|
|
1234
1412
|
const onboardingPolicy = core.getOnboardingPolicy();
|
|
1235
1413
|
const effective = buildAuthBehavior(core);
|
|
1414
|
+
const effectiveSite = buildSiteBehavior(core);
|
|
1236
1415
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1237
1416
|
res.end(JSON.stringify({
|
|
1238
1417
|
settings: {
|
|
@@ -1242,6 +1421,13 @@ export function registerAdminRoutes(deps) {
|
|
|
1242
1421
|
...(typeof authSettings.authRateLimitEnabled === 'boolean' ? { authRateLimitEnabled: authSettings.authRateLimitEnabled } : {}),
|
|
1243
1422
|
...(typeof authSettings.authRateLimitMaxRequests === 'number' ? { authRateLimitMaxRequests: authSettings.authRateLimitMaxRequests } : {}),
|
|
1244
1423
|
...(typeof authSettings.authRateLimitWindowMs === 'number' ? { authRateLimitWindowMs: authSettings.authRateLimitWindowMs } : {}),
|
|
1424
|
+
...(typeof siteSettings.showRunTaskforceLocally === 'boolean' ? { showRunTaskforceLocally: siteSettings.showRunTaskforceLocally } : {}),
|
|
1425
|
+
...(typeof siteSettings.pricingPageEnabled === 'boolean' ? { pricingPageEnabled: siteSettings.pricingPageEnabled } : {}),
|
|
1426
|
+
...(typeof siteSettings.animatedBackgroundEnabled === 'boolean' ? { animatedBackgroundEnabled: siteSettings.animatedBackgroundEnabled } : {}),
|
|
1427
|
+
...(siteSettings.siteTheme === 'light' || siteSettings.siteTheme === 'dark' ? { siteTheme: siteSettings.siteTheme } : {}),
|
|
1428
|
+
...(typeof siteSettings.pricingPageTitle === 'string' ? { pricingPageTitle: siteSettings.pricingPageTitle } : {}),
|
|
1429
|
+
...(typeof siteSettings.pricingPageDescription === 'string' ? { pricingPageDescription: siteSettings.pricingPageDescription } : {}),
|
|
1430
|
+
...(typeof siteSettings.pricingPageShowPublicDescriptions === 'boolean' ? { pricingPageShowPublicDescriptions: siteSettings.pricingPageShowPublicDescriptions } : {}),
|
|
1245
1431
|
onboardingPolicyMode: onboardingPolicy.mode,
|
|
1246
1432
|
onboardingPlanId: onboardingPolicy.planId
|
|
1247
1433
|
},
|
|
@@ -1252,6 +1438,13 @@ export function registerAdminRoutes(deps) {
|
|
|
1252
1438
|
authRateLimitEnabled: effective.authRateLimitEnabled,
|
|
1253
1439
|
authRateLimitMaxRequests: effective.authRateLimitMaxRequests,
|
|
1254
1440
|
authRateLimitWindowMs: effective.authRateLimitWindowMs,
|
|
1441
|
+
showRunTaskforceLocally: effectiveSite.showRunTaskforceLocally,
|
|
1442
|
+
pricingPageEnabled: effectiveSite.pricingPageEnabled,
|
|
1443
|
+
animatedBackgroundEnabled: effectiveSite.animatedBackgroundEnabled,
|
|
1444
|
+
siteTheme: effectiveSite.siteTheme,
|
|
1445
|
+
pricingPageTitle: effectiveSite.pricingPageTitle,
|
|
1446
|
+
pricingPageDescription: effectiveSite.pricingPageDescription,
|
|
1447
|
+
pricingPageShowPublicDescriptions: effectiveSite.pricingPageShowPublicDescriptions,
|
|
1255
1448
|
onboardingPolicyMode: effective.onboardingPolicyMode,
|
|
1256
1449
|
onboardingPlanId: effective.onboardingPlanId
|
|
1257
1450
|
}
|
|
@@ -1273,11 +1466,31 @@ export function registerAdminRoutes(deps) {
|
|
|
1273
1466
|
: {}),
|
|
1274
1467
|
...(typeof siteSettings.pricingPageEnabled === 'boolean'
|
|
1275
1468
|
? { pricingPageEnabled: siteSettings.pricingPageEnabled }
|
|
1469
|
+
: {}),
|
|
1470
|
+
...(typeof siteSettings.animatedBackgroundEnabled === 'boolean'
|
|
1471
|
+
? { animatedBackgroundEnabled: siteSettings.animatedBackgroundEnabled }
|
|
1472
|
+
: {}),
|
|
1473
|
+
...(siteSettings.siteTheme === 'light' || siteSettings.siteTheme === 'dark'
|
|
1474
|
+
? { siteTheme: siteSettings.siteTheme }
|
|
1475
|
+
: {}),
|
|
1476
|
+
...(typeof siteSettings.pricingPageTitle === 'string'
|
|
1477
|
+
? { pricingPageTitle: siteSettings.pricingPageTitle }
|
|
1478
|
+
: {}),
|
|
1479
|
+
...(typeof siteSettings.pricingPageDescription === 'string'
|
|
1480
|
+
? { pricingPageDescription: siteSettings.pricingPageDescription }
|
|
1481
|
+
: {}),
|
|
1482
|
+
...(typeof siteSettings.pricingPageShowPublicDescriptions === 'boolean'
|
|
1483
|
+
? { pricingPageShowPublicDescriptions: siteSettings.pricingPageShowPublicDescriptions }
|
|
1276
1484
|
: {})
|
|
1277
1485
|
},
|
|
1278
1486
|
status: {
|
|
1279
1487
|
showRunTaskforceLocally: siteBehavior.showRunTaskforceLocally,
|
|
1280
|
-
pricingPageEnabled: siteBehavior.pricingPageEnabled
|
|
1488
|
+
pricingPageEnabled: siteBehavior.pricingPageEnabled,
|
|
1489
|
+
animatedBackgroundEnabled: siteBehavior.animatedBackgroundEnabled,
|
|
1490
|
+
siteTheme: siteBehavior.siteTheme,
|
|
1491
|
+
pricingPageTitle: siteBehavior.pricingPageTitle,
|
|
1492
|
+
pricingPageDescription: siteBehavior.pricingPageDescription,
|
|
1493
|
+
pricingPageShowPublicDescriptions: siteBehavior.pricingPageShowPublicDescriptions
|
|
1281
1494
|
}
|
|
1282
1495
|
}));
|
|
1283
1496
|
};
|
|
@@ -1448,9 +1661,11 @@ export function registerAdminRoutes(deps) {
|
|
|
1448
1661
|
}
|
|
1449
1662
|
const reqUrl = new URL(req.url || '/api/taskforce/admin/orphaned-assets', 'http://localhost');
|
|
1450
1663
|
const workspaceId = String(reqUrl.searchParams.get('workspaceId') || requestWorkspaceId(req) || '').trim();
|
|
1664
|
+
const aiProfileAvatarStorageKeys = listReferencedAiProfileAvatarStorageKeys(workspaceId);
|
|
1451
1665
|
const assets = workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
1452
1666
|
includeDeleted: false
|
|
1453
|
-
}).filter((asset) =>
|
|
1667
|
+
}).filter((asset) => (!aiProfileAvatarStorageKeys.has(asset.storageKey)
|
|
1668
|
+
&& workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId).every((link) => !!link.deletedAt)))
|
|
1454
1669
|
.map((asset) => ({
|
|
1455
1670
|
assetId: asset.assetId,
|
|
1456
1671
|
workspaceId: asset.workspaceId,
|
|
@@ -1529,6 +1744,11 @@ export function registerAdminRoutes(deps) {
|
|
|
1529
1744
|
res.end(JSON.stringify({ error: 'Asset not found.' }));
|
|
1530
1745
|
return;
|
|
1531
1746
|
}
|
|
1747
|
+
if (listReferencedAiProfileAvatarStorageKeys(workspaceId).has(asset.storageKey)) {
|
|
1748
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
1749
|
+
res.end(JSON.stringify({ error: 'Asset is still referenced by an AI profile avatar.' }));
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1532
1752
|
const objectStorageClient = getObjectStorageClient();
|
|
1533
1753
|
const { basePath } = core.getPaths();
|
|
1534
1754
|
try {
|
|
@@ -1891,6 +2111,25 @@ export function registerAdminRoutes(deps) {
|
|
|
1891
2111
|
: body?.onboardingPlanId === null
|
|
1892
2112
|
? null
|
|
1893
2113
|
: currentOnboardingPolicy.planId;
|
|
2114
|
+
const pricingPageTitle = typeof body?.pricingPageTitle === 'string'
|
|
2115
|
+
? body.pricingPageTitle.trim()
|
|
2116
|
+
: undefined;
|
|
2117
|
+
const pricingPageDescription = typeof body?.pricingPageDescription === 'string'
|
|
2118
|
+
? body.pricingPageDescription.trim()
|
|
2119
|
+
: undefined;
|
|
2120
|
+
const siteTheme = body?.siteTheme === 'light' || body?.siteTheme === 'dark'
|
|
2121
|
+
? body.siteTheme
|
|
2122
|
+
: undefined;
|
|
2123
|
+
if (pricingPageTitle !== undefined && pricingPageTitle.length > 120) {
|
|
2124
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2125
|
+
res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
if (pricingPageDescription !== undefined && pricingPageDescription.length > PRICING_PAGE_DESCRIPTION_MAX_LENGTH) {
|
|
2129
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2130
|
+
res.end(JSON.stringify({ error: `Pricing page description must be ${PRICING_PAGE_DESCRIPTION_MAX_LENGTH} characters or fewer.`, code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
1894
2133
|
const validation = core.validateOnboardingPolicy({
|
|
1895
2134
|
mode: onboardingPolicyMode,
|
|
1896
2135
|
planId: onboardingPlanId
|
|
@@ -1900,6 +2139,29 @@ export function registerAdminRoutes(deps) {
|
|
|
1900
2139
|
res.end(JSON.stringify({ error: validation.message || 'Invalid onboarding policy.', code: validation.code, details: validation.details }));
|
|
1901
2140
|
return;
|
|
1902
2141
|
}
|
|
2142
|
+
const siteSettingsUpdate = {
|
|
2143
|
+
...(typeof body?.showRunTaskforceLocally === 'boolean'
|
|
2144
|
+
? { showRunTaskforceLocally: body.showRunTaskforceLocally }
|
|
2145
|
+
: {}),
|
|
2146
|
+
...(typeof body?.pricingPageEnabled === 'boolean'
|
|
2147
|
+
? { pricingPageEnabled: body.pricingPageEnabled }
|
|
2148
|
+
: {}),
|
|
2149
|
+
...(typeof body?.animatedBackgroundEnabled === 'boolean'
|
|
2150
|
+
? { animatedBackgroundEnabled: body.animatedBackgroundEnabled }
|
|
2151
|
+
: {}),
|
|
2152
|
+
...(siteTheme !== undefined
|
|
2153
|
+
? { siteTheme }
|
|
2154
|
+
: {}),
|
|
2155
|
+
...(pricingPageTitle !== undefined
|
|
2156
|
+
? { pricingPageTitle }
|
|
2157
|
+
: {}),
|
|
2158
|
+
...(pricingPageDescription !== undefined
|
|
2159
|
+
? { pricingPageDescription }
|
|
2160
|
+
: {}),
|
|
2161
|
+
...(typeof body?.pricingPageShowPublicDescriptions === 'boolean'
|
|
2162
|
+
? { pricingPageShowPublicDescriptions: body.pricingPageShowPublicDescriptions }
|
|
2163
|
+
: {})
|
|
2164
|
+
};
|
|
1903
2165
|
core.updateGlobalSettings({
|
|
1904
2166
|
auth: {
|
|
1905
2167
|
...(typeof body?.allowSelfRegistration === 'boolean' ? { allowSelfRegistration: body.allowSelfRegistration } : {}),
|
|
@@ -1916,7 +2178,10 @@ export function registerAdminRoutes(deps) {
|
|
|
1916
2178
|
mode: onboardingPolicyMode,
|
|
1917
2179
|
planId: onboardingPlanId ?? null
|
|
1918
2180
|
}
|
|
1919
|
-
}
|
|
2181
|
+
},
|
|
2182
|
+
...(Object.keys(siteSettingsUpdate).length > 0
|
|
2183
|
+
? { site: siteSettingsUpdate }
|
|
2184
|
+
: {})
|
|
1920
2185
|
});
|
|
1921
2186
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1922
2187
|
res.end(JSON.stringify({ success: true }));
|
|
@@ -1928,6 +2193,25 @@ export function registerAdminRoutes(deps) {
|
|
|
1928
2193
|
return;
|
|
1929
2194
|
}
|
|
1930
2195
|
const body = await parseJsonBody(req);
|
|
2196
|
+
const pricingPageTitle = typeof body?.pricingPageTitle === 'string'
|
|
2197
|
+
? body.pricingPageTitle.trim()
|
|
2198
|
+
: undefined;
|
|
2199
|
+
const pricingPageDescription = typeof body?.pricingPageDescription === 'string'
|
|
2200
|
+
? body.pricingPageDescription.trim()
|
|
2201
|
+
: undefined;
|
|
2202
|
+
const siteTheme = body?.siteTheme === 'light' || body?.siteTheme === 'dark'
|
|
2203
|
+
? body.siteTheme
|
|
2204
|
+
: undefined;
|
|
2205
|
+
if (pricingPageTitle !== undefined && pricingPageTitle.length > 120) {
|
|
2206
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2207
|
+
res.end(JSON.stringify({ error: 'Pricing page title must be 120 characters or fewer.', code: 'PRICING_PAGE_TITLE_TOO_LONG' }));
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
if (pricingPageDescription !== undefined && pricingPageDescription.length > PRICING_PAGE_DESCRIPTION_MAX_LENGTH) {
|
|
2211
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2212
|
+
res.end(JSON.stringify({ error: `Pricing page description must be ${PRICING_PAGE_DESCRIPTION_MAX_LENGTH} characters or fewer.`, code: 'PRICING_PAGE_DESCRIPTION_TOO_LONG' }));
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
1931
2215
|
core.updateGlobalSettings({
|
|
1932
2216
|
site: {
|
|
1933
2217
|
...(typeof body?.showRunTaskforceLocally === 'boolean'
|
|
@@ -1935,6 +2219,21 @@ export function registerAdminRoutes(deps) {
|
|
|
1935
2219
|
: {}),
|
|
1936
2220
|
...(typeof body?.pricingPageEnabled === 'boolean'
|
|
1937
2221
|
? { pricingPageEnabled: body.pricingPageEnabled }
|
|
2222
|
+
: {}),
|
|
2223
|
+
...(typeof body?.animatedBackgroundEnabled === 'boolean'
|
|
2224
|
+
? { animatedBackgroundEnabled: body.animatedBackgroundEnabled }
|
|
2225
|
+
: {}),
|
|
2226
|
+
...(siteTheme !== undefined
|
|
2227
|
+
? { siteTheme }
|
|
2228
|
+
: {}),
|
|
2229
|
+
...(pricingPageTitle !== undefined
|
|
2230
|
+
? { pricingPageTitle }
|
|
2231
|
+
: {}),
|
|
2232
|
+
...(pricingPageDescription !== undefined
|
|
2233
|
+
? { pricingPageDescription }
|
|
2234
|
+
: {}),
|
|
2235
|
+
...(typeof body?.pricingPageShowPublicDescriptions === 'boolean'
|
|
2236
|
+
? { pricingPageShowPublicDescriptions: body.pricingPageShowPublicDescriptions }
|
|
1938
2237
|
: {})
|
|
1939
2238
|
}
|
|
1940
2239
|
});
|
|
@@ -2509,6 +2808,26 @@ export function registerAdminRoutes(deps) {
|
|
|
2509
2808
|
res.end(JSON.stringify({ success: false, error: error.message, code: error?.code || 'MCP_TOKEN_REVOKE_FAILED' }));
|
|
2510
2809
|
}
|
|
2511
2810
|
});
|
|
2811
|
+
addRoute('DELETE', '/api/taskforce/settings/mcp/tokens/:id', async (req, res, params) => {
|
|
2812
|
+
if (!ensureAppAccess(req, res))
|
|
2813
|
+
return;
|
|
2814
|
+
const userId = resolveAuthenticatedUserId(req);
|
|
2815
|
+
if (!userId || userId === 'anonymous') {
|
|
2816
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
2817
|
+
res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
try {
|
|
2821
|
+
const deleted = core.deleteMcpAccessToken({ actorUserId: userId, workspaceId: requestWorkspaceId(req), tokenId: params.id });
|
|
2822
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2823
|
+
res.end(JSON.stringify({ success: true, deleted }));
|
|
2824
|
+
}
|
|
2825
|
+
catch (error) {
|
|
2826
|
+
const statusCode = resolveErrorStatusCode(error);
|
|
2827
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
2828
|
+
res.end(JSON.stringify({ success: false, error: error.message, code: error?.code || 'MCP_TOKEN_DELETE_FAILED' }));
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2512
2831
|
addRoute('POST', '/api/taskforce/settings/mcp/tokens/:id/regenerate', async (req, res, params) => {
|
|
2513
2832
|
if (!ensureAppAccess(req, res))
|
|
2514
2833
|
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;
|