@taskforcehq/taskforce 0.3.319 → 0.3.321
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/dist/TaskforceCore.js +65 -19
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +29 -5
- package/dist/mcp/runtime.js +72 -10
- package/dist/mcp/taskPlanningRegistrar.js +1 -0
- package/dist/server/routes/admin.js +40 -29
- package/dist/server/routes/auth.js +24 -0
- package/dist/server/routes.js +18 -27
- package/dist/ui/assets/{AgentsModule-DxsN8GQI.js → AgentsModule-7jpDeuVR.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DUkRcfo7.js → AnnotatedAttachmentWorkspace-BJLe8DFj.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-D_uqJ94r.js → ContextAttachmentManager-AmEny3eI.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DAsuvSWR.js → DocumentWorkspace-Cqdq31Ha.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-QLUNczJS.js → EntityActivityTimeline-BlS7DhA4.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-D23morXe.js → InitiativesModule-C1qfO1uZ.js} +1 -1
- package/dist/ui/assets/{PlansPage-X6c0yJ1a.js → PlansPage-DFt5w-0Y.js} +1 -1
- package/dist/ui/assets/{TaskContextUpload-BERMUT_S.js → TaskContextUpload-zuro5gDT.js} +1 -1
- package/dist/ui/assets/{TaskSettings-DlX_5CSg.js → TaskSettings-CJPC2Bft.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-CVu4lhC1.js → WorkflowsModule-CEEGuxsf.js} +1 -1
- package/dist/ui/assets/documentReferences-Ca2P--i5.js +1 -0
- package/dist/ui/assets/index-CGDQY0nN.js +5 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/documentReferences-BCXQQ60P.js +0 -1
- package/dist/ui/assets/index-CdlMnBxP.js +0 -5
package/dist/TaskforceCore.js
CHANGED
|
@@ -54,6 +54,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
54
54
|
const isPricingRoute = location.pathname === '/pricing';
|
|
55
55
|
const isPlansRoute = location.pathname === '/plans';
|
|
56
56
|
const isPlansScreen = location.pathname === '/' && String(routeQuery.get('screen') || '').trim().toLowerCase() === 'plans';
|
|
57
|
+
const plansPath = buildPlansPath();
|
|
57
58
|
const planSelectionPath = buildPlansPath({ gate: 'plan_selection_required' });
|
|
58
59
|
const navigate = useNavigate();
|
|
59
60
|
const [loginEmail, setLoginEmail] = useState('');
|
|
@@ -579,14 +580,44 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
579
580
|
persistWorkspaceSyncStatePatch,
|
|
580
581
|
refreshSetupContext
|
|
581
582
|
]);
|
|
582
|
-
const
|
|
583
|
+
const nextTarget = useMemo(() => {
|
|
583
584
|
const raw = new URLSearchParams(location.search).get('next') || '/';
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
585
|
+
if (raw.startsWith('/')) {
|
|
586
|
+
if (raw === '/login')
|
|
587
|
+
return '/';
|
|
588
|
+
return raw;
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
const parsed = new URL(raw);
|
|
592
|
+
const host = parsed.hostname.toLowerCase();
|
|
593
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
594
|
+
const isTaskforceHost = host === 'taskforcehq.ai' || host.endsWith('.taskforcehq.ai');
|
|
595
|
+
if ((parsed.protocol === 'http:' || parsed.protocol === 'https:') && (isLocal || isTaskforceHost)) {
|
|
596
|
+
return parsed.toString();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
587
600
|
return '/';
|
|
588
|
-
|
|
601
|
+
}
|
|
602
|
+
return '/';
|
|
589
603
|
}, [location.search]);
|
|
604
|
+
const nextPath = useMemo(() => nextTarget.startsWith('/') ? nextTarget : '/', [nextTarget]);
|
|
605
|
+
const navigateToPostAuthTarget = useCallback((target, options) => {
|
|
606
|
+
if (!target)
|
|
607
|
+
return;
|
|
608
|
+
if (target.startsWith('/')) {
|
|
609
|
+
navigate(target, { replace: options?.replace === true });
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (typeof window !== 'undefined') {
|
|
613
|
+
if (options?.replace === true) {
|
|
614
|
+
window.location.replace(target);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
window.location.assign(target);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}, [navigate]);
|
|
590
621
|
const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
|
|
591
622
|
try {
|
|
592
623
|
const parsed = new URL(nextPath, 'https://taskforce.local');
|
|
@@ -622,9 +653,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
622
653
|
interval: selectedSignupPlanVersionId ? resolvedSignupInterval : null
|
|
623
654
|
});
|
|
624
655
|
}
|
|
625
|
-
return
|
|
656
|
+
return nextTarget;
|
|
626
657
|
}, [
|
|
627
|
-
|
|
658
|
+
nextTarget,
|
|
628
659
|
planSelectionPath,
|
|
629
660
|
resolvedSignupInterval,
|
|
630
661
|
selectedSignupPlanId,
|
|
@@ -688,9 +719,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
688
719
|
? resolveWorkspaceSetupPath()
|
|
689
720
|
: pendingPostAuthRedirect.path;
|
|
690
721
|
const currentRoute = `${location.pathname}${location.search}${location.hash || ''}`;
|
|
691
|
-
if (
|
|
692
|
-
|
|
722
|
+
if (destination.startsWith('/')) {
|
|
723
|
+
if (currentRoute !== destination) {
|
|
724
|
+
navigate(destination, { replace: true });
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
693
727
|
}
|
|
728
|
+
navigateToPostAuthTarget(destination, { replace: true });
|
|
694
729
|
}, [
|
|
695
730
|
authSessionResolved,
|
|
696
731
|
cloudAccountAccessPending,
|
|
@@ -699,6 +734,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
699
734
|
location.pathname,
|
|
700
735
|
location.search,
|
|
701
736
|
navigate,
|
|
737
|
+
navigateToPostAuthTarget,
|
|
702
738
|
pendingPostAuthRedirect,
|
|
703
739
|
resolveWorkspaceSetupPath
|
|
704
740
|
]);
|
|
@@ -726,9 +762,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
726
762
|
? pendingPostAuthRedirect.path
|
|
727
763
|
: planSelectionPath;
|
|
728
764
|
const currentRoute = `${location.pathname}${location.search}${location.hash || ''}`;
|
|
729
|
-
if (
|
|
730
|
-
|
|
765
|
+
if (destination.startsWith('/')) {
|
|
766
|
+
if (currentRoute !== destination) {
|
|
767
|
+
navigate(destination, { replace: true });
|
|
768
|
+
}
|
|
769
|
+
return;
|
|
731
770
|
}
|
|
771
|
+
navigateToPostAuthTarget(destination, { replace: true });
|
|
732
772
|
}, [
|
|
733
773
|
accountAccessGate?.canAccessApp,
|
|
734
774
|
accountAccessGateResolved,
|
|
@@ -739,6 +779,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
739
779
|
location.pathname,
|
|
740
780
|
location.search,
|
|
741
781
|
navigate,
|
|
782
|
+
navigateToPostAuthTarget,
|
|
742
783
|
pendingPostAuthRedirect,
|
|
743
784
|
planSelectionPath
|
|
744
785
|
]);
|
|
@@ -901,8 +942,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
901
942
|
if (shouldDelayAuthenticatedLoginRedirect) {
|
|
902
943
|
return;
|
|
903
944
|
}
|
|
904
|
-
if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
|
|
905
|
-
|
|
945
|
+
if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute) {
|
|
946
|
+
navigateToPostAuthTarget(nextTarget, { replace: true });
|
|
947
|
+
}
|
|
906
948
|
}
|
|
907
949
|
else {
|
|
908
950
|
if (!isLoginRoute) {
|
|
@@ -1243,7 +1285,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1243
1285
|
|| pendingVerificationCommercialState === 'checkout_pending';
|
|
1244
1286
|
setCommercialOnboardingRedirectActive(commercialOnboardingGate);
|
|
1245
1287
|
setPendingPostAuthRedirect({
|
|
1246
|
-
path: pendingVerificationRedirectPath ||
|
|
1288
|
+
path: pendingVerificationRedirectPath || nextTarget,
|
|
1247
1289
|
workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
|
|
1248
1290
|
commercialOnboardingGate
|
|
1249
1291
|
});
|
|
@@ -1299,7 +1341,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1299
1341
|
setLoginBusy(false);
|
|
1300
1342
|
return;
|
|
1301
1343
|
}
|
|
1302
|
-
|
|
1344
|
+
navigateToPostAuthTarget(nextTarget, { replace: true });
|
|
1303
1345
|
}
|
|
1304
1346
|
else if (authMode === 'login') {
|
|
1305
1347
|
if (inviteToken.trim()) {
|
|
@@ -1319,14 +1361,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1319
1361
|
setLoginDisplayName('');
|
|
1320
1362
|
setLoginPassword('');
|
|
1321
1363
|
setLoginPasswordConfirm('');
|
|
1322
|
-
|
|
1364
|
+
navigateToPostAuthTarget(nextTarget, { replace: true });
|
|
1323
1365
|
}
|
|
1324
1366
|
else {
|
|
1325
1367
|
setLoginEmail('');
|
|
1326
1368
|
setLoginDisplayName('');
|
|
1327
1369
|
setLoginPassword('');
|
|
1328
1370
|
setLoginPasswordConfirm('');
|
|
1329
|
-
|
|
1371
|
+
navigateToPostAuthTarget(nextTarget, { replace: true });
|
|
1330
1372
|
}
|
|
1331
1373
|
setLoginBusy(false);
|
|
1332
1374
|
}, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
|
|
@@ -1567,6 +1609,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1567
1609
|
});
|
|
1568
1610
|
const result = createResult || saveResult;
|
|
1569
1611
|
if (!result?.success) {
|
|
1612
|
+
if (result?.code === 'WORKSPACE_LIMIT_REACHED') {
|
|
1613
|
+
setSetupBusy(false);
|
|
1614
|
+
navigate(plansPath, { replace: true });
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1570
1617
|
setSetupError(result?.error || t('setup.saveWorkspaceFailed'));
|
|
1571
1618
|
}
|
|
1572
1619
|
else {
|
|
@@ -1579,8 +1626,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1579
1626
|
setSetupBusy(false);
|
|
1580
1627
|
return;
|
|
1581
1628
|
}
|
|
1582
|
-
const shouldApplyStarterPack =
|
|
1583
|
-
&& Boolean(selectedSetupPack)
|
|
1629
|
+
const shouldApplyStarterPack = Boolean(selectedSetupPack)
|
|
1584
1630
|
&& (setupTaxonomySections.categories || setupTaxonomySections.types || setupTaxonomySections.priorities);
|
|
1585
1631
|
if (shouldApplyStarterPack && selectedSetupPack) {
|
|
1586
1632
|
const applyResult = await settingsModel.onApplySystemTaxonomyPack({
|
|
@@ -622,10 +622,34 @@ export function useTaskforceWorkspaceBootstrap(args) {
|
|
|
622
622
|
setWorkspaceBootstrapPending(true);
|
|
623
623
|
try {
|
|
624
624
|
await fetchBootstrapConfig({ ignoreAuthGuard });
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
625
|
+
const workspaceId = String(currentWorkspaceIdRef.current || 'default').trim() || 'default';
|
|
626
|
+
const deferredStartedAt = getBootstrapTimestamp();
|
|
627
|
+
void fetchTasks(tasksSilent, { ignoreAuthGuard }).then(() => {
|
|
628
|
+
logBootstrapDebug('cloud_bootstrap_tasks_loaded_deferred', {
|
|
629
|
+
reason,
|
|
630
|
+
workspaceId,
|
|
631
|
+
durationMs: Math.max(0, Math.round(getBootstrapTimestamp() - deferredStartedAt))
|
|
632
|
+
});
|
|
633
|
+
}).catch((error) => {
|
|
634
|
+
logBootstrapDebug('cloud_bootstrap_tasks_deferred_failed', {
|
|
635
|
+
reason,
|
|
636
|
+
workspaceId,
|
|
637
|
+
error: error instanceof Error ? error.message : String(error)
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
void fetchReferenceData({ ignoreAuthGuard }).then(() => {
|
|
641
|
+
logBootstrapDebug('cloud_bootstrap_reference_data_loaded_deferred', {
|
|
642
|
+
reason,
|
|
643
|
+
workspaceId,
|
|
644
|
+
durationMs: Math.max(0, Math.round(getBootstrapTimestamp() - deferredStartedAt))
|
|
645
|
+
});
|
|
646
|
+
}).catch((error) => {
|
|
647
|
+
logBootstrapDebug('cloud_bootstrap_reference_data_deferred_failed', {
|
|
648
|
+
reason,
|
|
649
|
+
workspaceId,
|
|
650
|
+
error: error instanceof Error ? error.message : String(error)
|
|
651
|
+
});
|
|
652
|
+
});
|
|
629
653
|
void fetchPlanningEntities({ ignoreAuthGuard });
|
|
630
654
|
if (includeDeferredArchive) {
|
|
631
655
|
scheduleDeferredArchiveBootstrap();
|
|
@@ -636,7 +660,7 @@ export function useTaskforceWorkspaceBootstrap(args) {
|
|
|
636
660
|
authenticated: true,
|
|
637
661
|
workspaceSetupRequired: false,
|
|
638
662
|
success: true,
|
|
639
|
-
workspaceId
|
|
663
|
+
workspaceId
|
|
640
664
|
});
|
|
641
665
|
return { success: true, authenticated: true, workspaceSetupRequired: false };
|
|
642
666
|
}
|
package/dist/mcp/runtime.js
CHANGED
|
@@ -740,6 +740,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
740
740
|
}))
|
|
741
741
|
.filter((actor) => Boolean(actor.id));
|
|
742
742
|
}
|
|
743
|
+
function buildTaskWriteSummary(task) {
|
|
744
|
+
return {
|
|
745
|
+
id: task?.id || null,
|
|
746
|
+
referenceLabel: getTaskReferenceLabel(task) || null,
|
|
747
|
+
title: task?.title || null,
|
|
748
|
+
status: task?.status || null,
|
|
749
|
+
assignee: task?.assignee || null,
|
|
750
|
+
workstreamId: task?.workstreamId || null,
|
|
751
|
+
scheduledDate: task?.scheduledDate || null,
|
|
752
|
+
dueDate: task?.dueDate || null,
|
|
753
|
+
scheduledWeekKey: task?.scheduledWeekKey || null,
|
|
754
|
+
orderInDay: task?.orderInDay ?? null,
|
|
755
|
+
updatedAt: task?.updatedAt || null,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
743
758
|
function buildAiProfileDescriptor(profile) {
|
|
744
759
|
return {
|
|
745
760
|
id: profile.id,
|
|
@@ -2178,7 +2193,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2178
2193
|
const assigneeHint = describeEligibleAssignees();
|
|
2179
2194
|
const mutatingProfileTokenProperty = {
|
|
2180
2195
|
type: "string",
|
|
2181
|
-
description: "Optional AI profile token returned by resolve_profile. Pass this when your MCP client does not preserve session identity between calls."
|
|
2196
|
+
description: "Optional AI profile token returned by resolve_profile. Pass this when your MCP client does not preserve session identity between calls. In stateless/cloud MCP clients without ambient AI binding, this is usually required on mutating calls after bootstrap."
|
|
2182
2197
|
};
|
|
2183
2198
|
const prefix = `[Project: ${PROJECT_NAME}] `;
|
|
2184
2199
|
const agentNote = runtimeMode === 'local'
|
|
@@ -2190,11 +2205,32 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2190
2205
|
.filter(Boolean)
|
|
2191
2206
|
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
2192
2207
|
.join(' ');
|
|
2208
|
+
const nonDestructiveWriteTools = new Set([
|
|
2209
|
+
'add_annotated_attachment_marker',
|
|
2210
|
+
'add_comment',
|
|
2211
|
+
'add_document_review_comment',
|
|
2212
|
+
'bulk_add_annotated_attachment_markers',
|
|
2213
|
+
'create_annotated_attachment_session',
|
|
2214
|
+
'create_document_review_session',
|
|
2215
|
+
'create_initiative',
|
|
2216
|
+
'create_task',
|
|
2217
|
+
'create_workstream',
|
|
2218
|
+
'link_task_context',
|
|
2219
|
+
'resolve_profile',
|
|
2220
|
+
'review_document',
|
|
2221
|
+
'review_image',
|
|
2222
|
+
]);
|
|
2193
2223
|
const annotationsForTool = (name) => {
|
|
2194
2224
|
const title = titleForTool(name);
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2225
|
+
if (!isMcpWriteToolName(name)) {
|
|
2226
|
+
return { title, readOnlyHint: true, destructiveHint: false, openWorldHint: false };
|
|
2227
|
+
}
|
|
2228
|
+
return {
|
|
2229
|
+
title,
|
|
2230
|
+
readOnlyHint: false,
|
|
2231
|
+
destructiveHint: !nonDestructiveWriteTools.has(name),
|
|
2232
|
+
openWorldHint: false,
|
|
2233
|
+
};
|
|
2198
2234
|
};
|
|
2199
2235
|
const tools = registeredTools
|
|
2200
2236
|
.map((tool) => tool.buildDescriptor({
|
|
@@ -2915,7 +2951,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2915
2951
|
workstreamIds: [resolvedWorkstreamId],
|
|
2916
2952
|
});
|
|
2917
2953
|
return {
|
|
2918
|
-
content: [{
|
|
2954
|
+
content: [{
|
|
2955
|
+
type: "text",
|
|
2956
|
+
text: JSON.stringify({
|
|
2957
|
+
task: buildTaskWriteSummary(updated),
|
|
2958
|
+
workstreamId: resolvedWorkstreamId,
|
|
2959
|
+
}, null, 2)
|
|
2960
|
+
}],
|
|
2919
2961
|
};
|
|
2920
2962
|
}
|
|
2921
2963
|
case "clear_task_workstream": {
|
|
@@ -2935,7 +2977,14 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2935
2977
|
...(priorWorkstreamId ? { includeWorkstreams: true, workstreamIds: [priorWorkstreamId] } : {}),
|
|
2936
2978
|
});
|
|
2937
2979
|
return {
|
|
2938
|
-
content: [{
|
|
2980
|
+
content: [{
|
|
2981
|
+
type: "text",
|
|
2982
|
+
text: JSON.stringify({
|
|
2983
|
+
task: buildTaskWriteSummary(updated),
|
|
2984
|
+
workstreamId: null,
|
|
2985
|
+
previousWorkstreamId: priorWorkstreamId,
|
|
2986
|
+
}, null, 2)
|
|
2987
|
+
}],
|
|
2939
2988
|
};
|
|
2940
2989
|
}
|
|
2941
2990
|
case "set_workstream_initiative": {
|
|
@@ -4001,7 +4050,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4001
4050
|
persistedOrderInDay: result.task.orderInDay ?? null
|
|
4002
4051
|
},
|
|
4003
4052
|
warnings: result.warnings,
|
|
4004
|
-
task: result.task
|
|
4053
|
+
task: buildTaskWriteSummary(result.task)
|
|
4005
4054
|
}, null, 2)
|
|
4006
4055
|
}],
|
|
4007
4056
|
};
|
|
@@ -4154,6 +4203,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4154
4203
|
priority: resolveNumberOption('priority', createArgs.priority, getActivePriorities().map(p => ({ value: p.value, label: p.label }))),
|
|
4155
4204
|
approach: resolveStringOption('approach', createArgs.approach, getActiveApproaches().map(a => ({ value: a.value, label: a.label }))),
|
|
4156
4205
|
assignee: resolveRuntimeTaskAssigneeInput(createAssigneeInput),
|
|
4206
|
+
workstreamId: createArgs.workstreamId ? resolveWorkstreamByIdentifierOrThrow(createArgs.workstreamId).id : undefined,
|
|
4157
4207
|
status: resolveStringOption('status', createArgs.status, STATUS_OPTIONS),
|
|
4158
4208
|
complexity: resolveNumberOption('complexity', createArgs.complexity, COMPLEXITY_OPTIONS)
|
|
4159
4209
|
};
|
|
@@ -4219,7 +4269,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4219
4269
|
content: [{
|
|
4220
4270
|
type: "text",
|
|
4221
4271
|
text: JSON.stringify({
|
|
4222
|
-
task: updated,
|
|
4272
|
+
task: buildTaskWriteSummary(updated),
|
|
4223
4273
|
started: true,
|
|
4224
4274
|
claimed: resolved.claimed && updated.assignee === activeAiProfileId,
|
|
4225
4275
|
statusChanged: resolved.previousStatus !== String(updated.status || '').trim(),
|
|
@@ -4294,7 +4344,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4294
4344
|
includeStats: true,
|
|
4295
4345
|
});
|
|
4296
4346
|
return {
|
|
4297
|
-
content: [{
|
|
4347
|
+
content: [{
|
|
4348
|
+
type: "text",
|
|
4349
|
+
text: JSON.stringify({
|
|
4350
|
+
task: buildTaskWriteSummary(updated),
|
|
4351
|
+
changedFields: Object.keys(finalUpdates),
|
|
4352
|
+
}, null, 2)
|
|
4353
|
+
}],
|
|
4298
4354
|
};
|
|
4299
4355
|
}
|
|
4300
4356
|
catch (error) {
|
|
@@ -4385,7 +4441,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4385
4441
|
includeStats: true,
|
|
4386
4442
|
});
|
|
4387
4443
|
return {
|
|
4388
|
-
content: [{
|
|
4444
|
+
content: [{
|
|
4445
|
+
type: "text",
|
|
4446
|
+
text: JSON.stringify({
|
|
4447
|
+
task: buildTaskWriteSummary(updated),
|
|
4448
|
+
assignee: resolvedAssignee,
|
|
4449
|
+
}, null, 2)
|
|
4450
|
+
}],
|
|
4389
4451
|
};
|
|
4390
4452
|
}
|
|
4391
4453
|
case "bulk_assign_tasks": {
|
|
@@ -353,6 +353,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
353
353
|
title: stringProperty('Task title.'),
|
|
354
354
|
description: stringProperty('Optional task description.'),
|
|
355
355
|
assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
|
|
356
|
+
workstreamId: stringProperty('Optional workstream ID or workstream reference such as WS-123. Use this to create the task directly inside a workstream without a separate set_task_workstream call.'),
|
|
356
357
|
category: stringProperty('Task category value.'),
|
|
357
358
|
type: stringProperty('Task type value.', context.typeEnum),
|
|
358
359
|
priority: numberProperty(context.priorityDesc),
|
|
@@ -262,7 +262,7 @@ export function registerAdminRoutes(deps) {
|
|
|
262
262
|
const scopeLabels = {
|
|
263
263
|
'tasks:read': 'Read tasks',
|
|
264
264
|
'tasks:write': 'Update tasks',
|
|
265
|
-
'workspace:read': 'Read workspace
|
|
265
|
+
'workspace:read': 'Read workspace'
|
|
266
266
|
};
|
|
267
267
|
const options = input.workspaces.map((workspace) => {
|
|
268
268
|
const selected = workspace.id === input.preselectedWorkspaceId ? ' selected' : '';
|
|
@@ -285,25 +285,19 @@ export function registerAdminRoutes(deps) {
|
|
|
285
285
|
:root { color-scheme: dark; }
|
|
286
286
|
body { margin: 0; font-family: "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: radial-gradient(circle at top, rgba(255,138,0,0.08), transparent 30%), #111126; color: #f8f7ff; }
|
|
287
287
|
.wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 32px 24px; }
|
|
288
|
-
.card { width: min(
|
|
289
|
-
|
|
290
|
-
.
|
|
291
|
-
h1 { margin: 0; font-size: clamp(2.25rem, 5vw, 3rem); line-height: 0.96; letter-spacing: -0.04em; color: #f8f7ff; }
|
|
292
|
-
.brand-word { font-family: "Russo One", sans-serif; font-size: 0.94em; letter-spacing: -0.03em; }
|
|
293
|
-
.lede { margin: 18px 0 8px; font-size: 1.15rem; line-height: 1.5; color: rgba(255,255,255,0.78); max-width: 34ch; }
|
|
288
|
+
.card { width: min(720px, 100%); background: rgba(22,22,44,0.95); border: 1px solid rgba(255,255,255,0.08); border-radius: 32px; box-shadow: 0 24px 80px rgba(0,0,0,0.35); padding: 40px 48px; }
|
|
289
|
+
h1 { margin: 0; font-size: clamp(1.95rem, 4.2vw, 2.6rem); line-height: 1.02; letter-spacing: -0.035em; color: #f8f7ff; }
|
|
290
|
+
.lede { margin: 16px 0 8px; font-size: 1.1rem; line-height: 1.45; color: rgba(255,255,255,0.78); max-width: 48ch; text-wrap: pretty; }
|
|
294
291
|
.lede strong { color: #fff; }
|
|
295
|
-
.
|
|
292
|
+
.session-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 0 28px; }
|
|
293
|
+
.user-meta { margin: 0; color: rgba(255,255,255,0.5); font-size: 0.9rem; }
|
|
294
|
+
.account-switch { padding: 0; border: 0; min-width: 0; background: transparent; color: #cbd8ff; font-size: 0.9rem; font-weight: 600; box-shadow: none; }
|
|
295
|
+
.account-switch:hover { transform: none; color: #ffffff; text-decoration: underline; }
|
|
296
296
|
label { display: block; font-size: 13px; font-weight: 700; margin: 24px 0 10px; color: rgba(255,255,255,0.92); letter-spacing: 0.01em; }
|
|
297
297
|
select { width: 100%; border-radius: 18px; border: 1px solid rgba(255,255,255,0.1); background: #1f2039; color: #fff; padding: 16px 18px; font-size: 15px; font-weight: 600; box-shadow: inset 0 1px 0 rgba(255,255,255,0.02); }
|
|
298
298
|
.scopes { display:flex; flex-wrap:wrap; gap:10px; margin-top:8px; }
|
|
299
299
|
.scope-chip { display:inline-flex; padding:8px 14px; border-radius:999px; background:#2a2947; border:1px solid rgba(255,255,255,0.09); font-size:13px; font-weight:600; color:rgba(255,255,255,0.92); }
|
|
300
|
-
.
|
|
301
|
-
.details summary { list-style: none; cursor: pointer; padding: 16px 18px; font-size: 0.92rem; font-weight: 700; color: rgba(255,255,255,0.86); }
|
|
302
|
-
.details summary::-webkit-details-marker { display: none; }
|
|
303
|
-
.details-body { padding: 0 18px 18px; color: rgba(255,255,255,0.68); font-size: 0.9rem; line-height: 1.5; }
|
|
304
|
-
.details-row { margin: 0 0 8px; }
|
|
305
|
-
.details-row strong { color: rgba(255,255,255,0.92); }
|
|
306
|
-
.legal { margin: 24px 2px 0; color: rgba(255,255,255,0.58); font-size: 0.9rem; line-height: 1.55; max-width: 56ch; }
|
|
300
|
+
.legal { margin: 28px 2px 0; color: rgba(255,255,255,0.58); font-size: 0.9rem; line-height: 1.55; max-width: 58ch; }
|
|
307
301
|
.error { margin-bottom: 18px; padding: 12px 14px; border-radius: 14px; background: rgba(255,99,132,0.12); color: #ffb6c6; border: 1px solid rgba(255,99,132,0.25); }
|
|
308
302
|
.actions { display:flex; justify-content:flex-end; gap:14px; margin-top: 36px; }
|
|
309
303
|
button { border-radius: 18px; padding: 15px 22px; font-size: 15px; font-weight: 700; cursor: pointer; border: 1px solid rgba(255,255,255,0.08); min-width: 156px; transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease, background-color 160ms ease; }
|
|
@@ -313,7 +307,8 @@ export function registerAdminRoutes(deps) {
|
|
|
313
307
|
a { color: #cbd8ff; }
|
|
314
308
|
@media (max-width: 720px) {
|
|
315
309
|
.card { padding: 30px 22px; border-radius: 28px; }
|
|
316
|
-
h1 { font-size:
|
|
310
|
+
h1 { font-size: 1.85rem; }
|
|
311
|
+
.lede { max-width: none; }
|
|
317
312
|
.actions { flex-direction: column-reverse; }
|
|
318
313
|
button { width: 100%; }
|
|
319
314
|
}
|
|
@@ -322,22 +317,17 @@ export function registerAdminRoutes(deps) {
|
|
|
322
317
|
<body>
|
|
323
318
|
<div class="wrap">
|
|
324
319
|
<form class="card" method="post" action="/oauth/mcp/authorize">
|
|
325
|
-
<
|
|
326
|
-
<h1>Authorize <span class="brand-word">Taskforce</span> Connector</h1>
|
|
320
|
+
<h1>Authorize Taskforce Connector</h1>
|
|
327
321
|
<p class="lede"><strong>${escapeHtml(input.clientName)}</strong> wants to connect to Taskforce for <strong>${escapeHtml(input.userName)}</strong>.</p>
|
|
328
|
-
|
|
322
|
+
<div class="session-row">
|
|
323
|
+
${input.userMeta ? `<p class="user-meta">Signed in as ${escapeHtml(input.userMeta)}</p>` : ''}
|
|
324
|
+
<button class="account-switch" type="submit" formaction="/api/taskforce/auth/logout" formmethod="post" formnovalidate>Use different account</button>
|
|
325
|
+
</div>
|
|
329
326
|
${input.error ? `<div class="error">${escapeHtml(input.error)}</div>` : ''}
|
|
330
|
-
<label for="workspaceId">Workspace</label>
|
|
327
|
+
<label for="workspaceId">Select Workspace</label>
|
|
331
328
|
<select id="workspaceId" name="workspaceId" required>${options}</select>
|
|
332
329
|
<label>Requested Permissions</label>
|
|
333
330
|
<div class="scopes">${scopeBadges}</div>
|
|
334
|
-
<details class="details">
|
|
335
|
-
<summary>Technical details</summary>
|
|
336
|
-
<div class="details-body">
|
|
337
|
-
<div class="details-row"><strong>Client:</strong> ${escapeHtml(input.clientName)}</div>
|
|
338
|
-
<div class="details-row"><strong>Redirect URI:</strong> ${escapeHtml(input.redirectUri)}</div>
|
|
339
|
-
</div>
|
|
340
|
-
</details>
|
|
341
331
|
<p class="legal">By authorizing this connector, you agree to Taskforce's <a href="${termsUrl}" target="_blank" rel="noopener noreferrer">Terms of Service</a> and acknowledge the <a href="${privacyUrl}" target="_blank" rel="noopener noreferrer">Privacy Policy</a>.</p>
|
|
342
332
|
<input type="hidden" name="client_id" value="${escapeHtml(input.clientId)}" />
|
|
343
333
|
<input type="hidden" name="redirect_uri" value="${escapeHtml(input.redirectUri)}" />
|
|
@@ -345,6 +335,7 @@ export function registerAdminRoutes(deps) {
|
|
|
345
335
|
<input type="hidden" name="state" value="${escapeHtml(input.state)}" />
|
|
346
336
|
<input type="hidden" name="code_challenge" value="${escapeHtml(input.codeChallenge)}" />
|
|
347
337
|
<input type="hidden" name="code_challenge_method" value="${escapeHtml(input.codeChallengeMethod)}" />
|
|
338
|
+
<input type="hidden" name="next" value="${escapeHtml(input.authorizeUrl)}" />
|
|
348
339
|
<div class="actions">
|
|
349
340
|
<button type="submit" name="decision" value="deny">Cancel</button>
|
|
350
341
|
<button type="submit" name="decision" value="approve">Authorize</button>
|
|
@@ -2667,8 +2658,18 @@ export function registerAdminRoutes(deps) {
|
|
|
2667
2658
|
const userIdentity = core.resolveUserIdentity(access.userId);
|
|
2668
2659
|
const userName = String(userIdentity?.displayName || userIdentity?.email || access.userId).trim() || String(access.userId);
|
|
2669
2660
|
const userMeta = userIdentity?.email && userIdentity.email !== userName ? String(userIdentity.email).trim() : null;
|
|
2661
|
+
const authorizeUrl = new URL('/oauth/mcp/authorize', resolveMcpOauthIssuer(req));
|
|
2662
|
+
authorizeUrl.searchParams.set('client_id', clientId);
|
|
2663
|
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
|
|
2664
|
+
authorizeUrl.searchParams.set('response_type', responseType);
|
|
2665
|
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
2666
|
+
authorizeUrl.searchParams.set('code_challenge_method', codeChallengeMethod);
|
|
2667
|
+
if (state)
|
|
2668
|
+
authorizeUrl.searchParams.set('state', state);
|
|
2669
|
+
if (scopes.length > 0)
|
|
2670
|
+
authorizeUrl.searchParams.set('scope', scopes.join(' '));
|
|
2670
2671
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2671
|
-
res.end(renderMcpOauthAuthorizePage({ userName, userMeta, workspaces, preselectedWorkspaceId: preferredWorkspaceId, clientName: clientRecord.clientName, clientId: clientRecord.id, redirectUri, scopes, state, codeChallenge, codeChallengeMethod }));
|
|
2672
|
+
res.end(renderMcpOauthAuthorizePage({ userName, userMeta, authorizeUrl: authorizeUrl.toString(), workspaces, preselectedWorkspaceId: preferredWorkspaceId, clientName: clientRecord.clientName, clientId: clientRecord.id, redirectUri, scopes, state, codeChallenge, codeChallengeMethod }));
|
|
2672
2673
|
}
|
|
2673
2674
|
catch (error) {
|
|
2674
2675
|
const statusCode = resolveErrorStatusCode(error);
|
|
@@ -2713,8 +2714,18 @@ export function registerAdminRoutes(deps) {
|
|
|
2713
2714
|
const userIdentity = core.resolveUserIdentity(access.userId);
|
|
2714
2715
|
const userName = String(userIdentity?.displayName || userIdentity?.email || access.userId).trim() || String(access.userId);
|
|
2715
2716
|
const userMeta = userIdentity?.email && userIdentity.email !== userName ? String(userIdentity.email).trim() : null;
|
|
2717
|
+
const authorizeUrl = new URL('/oauth/mcp/authorize', resolveMcpOauthIssuer(req));
|
|
2718
|
+
authorizeUrl.searchParams.set('client_id', clientId);
|
|
2719
|
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
|
|
2720
|
+
authorizeUrl.searchParams.set('response_type', responseType);
|
|
2721
|
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
2722
|
+
authorizeUrl.searchParams.set('code_challenge_method', codeChallengeMethod);
|
|
2723
|
+
if (state)
|
|
2724
|
+
authorizeUrl.searchParams.set('state', state);
|
|
2725
|
+
if (scopes.length > 0)
|
|
2726
|
+
authorizeUrl.searchParams.set('scope', scopes.join(' '));
|
|
2716
2727
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2717
|
-
res.end(renderMcpOauthAuthorizePage({ userName, userMeta, workspaces, preselectedWorkspaceId: preferredWorkspaceId, clientName: clientRecord.clientName, clientId: clientRecord.id, redirectUri, scopes, state, codeChallenge, codeChallengeMethod }));
|
|
2728
|
+
res.end(renderMcpOauthAuthorizePage({ userName, userMeta, authorizeUrl: authorizeUrl.toString(), workspaces, preselectedWorkspaceId: preferredWorkspaceId, clientName: clientRecord.clientName, clientId: clientRecord.id, redirectUri, scopes, state, codeChallenge, codeChallengeMethod }));
|
|
2718
2729
|
}
|
|
2719
2730
|
catch (error) {
|
|
2720
2731
|
const statusCode = resolveErrorStatusCode(error);
|
|
@@ -2068,7 +2068,31 @@ export function registerAuthRoutes(deps) {
|
|
|
2068
2068
|
for (const domain of domains) {
|
|
2069
2069
|
clearCookies.push(`${clearBase}; Domain=${domain}`);
|
|
2070
2070
|
}
|
|
2071
|
+
const body = await parseFormBody(req);
|
|
2072
|
+
const requestedNext = String(body.next || '').trim();
|
|
2073
|
+
const next = (() => {
|
|
2074
|
+
if (!requestedNext)
|
|
2075
|
+
return '';
|
|
2076
|
+
try {
|
|
2077
|
+
const parsed = new URL(requestedNext);
|
|
2078
|
+
const host = parsed.hostname.toLowerCase();
|
|
2079
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
2080
|
+
const isTaskforceHost = host === 'taskforcehq.ai' || host.endsWith('.taskforcehq.ai');
|
|
2081
|
+
if ((parsed.protocol === 'http:' || parsed.protocol === 'https:') && (isLocal || isTaskforceHost)) {
|
|
2082
|
+
return parsed.toString();
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
catch {
|
|
2086
|
+
return '';
|
|
2087
|
+
}
|
|
2088
|
+
return '';
|
|
2089
|
+
})();
|
|
2071
2090
|
res.setHeader('Set-Cookie', [...new Set(clearCookies)]);
|
|
2091
|
+
if (next) {
|
|
2092
|
+
res.writeHead(303, { Location: next });
|
|
2093
|
+
res.end();
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2072
2096
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2073
2097
|
res.end(JSON.stringify({ success: true }));
|
|
2074
2098
|
});
|