@taskforcehq/taskforce 0.3.303 → 0.3.305
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 +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
@@ -44,6 +44,9 @@ function createEmptyReviewSessionsResponse() {
|
|
|
44
44
|
json: async () => ({ sessions: [] }),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
function renderDocumentWorkspace(props) {
|
|
48
|
+
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
49
|
+
}
|
|
47
50
|
describe('Document workspace management', () => {
|
|
48
51
|
beforeEach(() => {
|
|
49
52
|
fetchTaskforceApi.mockReset();
|
|
@@ -87,7 +90,7 @@ describe('Document workspace management', () => {
|
|
|
87
90
|
];
|
|
88
91
|
function Harness() {
|
|
89
92
|
const [selectedDoc, setSelectedDoc] = useState(null);
|
|
90
|
-
return (_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs, loading: false, error: null, selectedDoc: selectedDoc, searchQuery: "",
|
|
93
|
+
return (_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => doc.attachmentState === 'unattached'), loading: false, error: null, selectedDoc: selectedDoc, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: setSelectedDoc, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
91
94
|
}
|
|
92
95
|
render(_jsx(Harness, {}));
|
|
93
96
|
expect(screen.getByRole('button', { name: /Orphan notes/i })).toBeInTheDocument();
|
|
@@ -95,13 +98,13 @@ describe('Document workspace management', () => {
|
|
|
95
98
|
expect(screen.getByText('Unattached')).toBeInTheDocument();
|
|
96
99
|
});
|
|
97
100
|
it('keeps document search in the index without duplicate filter controls', () => {
|
|
98
|
-
|
|
101
|
+
renderDocumentWorkspace({ documents: [createDoc()] });
|
|
99
102
|
expect(screen.getByPlaceholderText('Search documents…')).toBeInTheDocument();
|
|
100
103
|
expect(screen.queryByRole('button', { name: 'Attached' })).not.toBeInTheDocument();
|
|
101
104
|
expect(screen.queryByRole('button', { name: 'Plans' })).not.toBeInTheDocument();
|
|
102
105
|
});
|
|
103
106
|
it('shows the document reference badge in the bottom metadata row of index cards', () => {
|
|
104
|
-
|
|
107
|
+
renderDocumentWorkspace({ documents: [createDoc({ referenceLabel: 'D-42' })] });
|
|
105
108
|
expect(screen.getByText('D-42')).toBeInTheDocument();
|
|
106
109
|
expect(screen.getByText(/64B/)).toBeInTheDocument();
|
|
107
110
|
});
|
|
@@ -142,6 +145,49 @@ describe('Document workspace management', () => {
|
|
|
142
145
|
confirmSpy.mockRestore();
|
|
143
146
|
alertSpy.mockRestore();
|
|
144
147
|
});
|
|
148
|
+
it('shows delete and unlink for attached docs and sends forceDetach on delete', async () => {
|
|
149
|
+
const user = userEvent.setup();
|
|
150
|
+
fetchTaskforceApi
|
|
151
|
+
.mockResolvedValueOnce({
|
|
152
|
+
ok: true,
|
|
153
|
+
text: async () => '# Example\n',
|
|
154
|
+
})
|
|
155
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
156
|
+
.mockResolvedValueOnce({
|
|
157
|
+
ok: true,
|
|
158
|
+
json: async () => ({ success: true, softDeleted: true, detachedTaskCount: 2 }),
|
|
159
|
+
});
|
|
160
|
+
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
|
|
161
|
+
const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => { });
|
|
162
|
+
const onDeleted = vi.fn();
|
|
163
|
+
render(_jsx(DocumentViewer, { doc: {
|
|
164
|
+
id: 'doc-attached',
|
|
165
|
+
assetId: 'asset-doc-attached',
|
|
166
|
+
fsPath: 'workspaces/default/assets/documents/doc-attached.md',
|
|
167
|
+
apiUrl: '/api/taskforce/documents/asset-doc-attached/content',
|
|
168
|
+
taskId: 'task-1',
|
|
169
|
+
displayName: 'Attached spec',
|
|
170
|
+
title: 'Attached spec',
|
|
171
|
+
docType: 'other',
|
|
172
|
+
sizeBytes: 64,
|
|
173
|
+
updatedAt: '2026-03-19T11:00:00.000Z',
|
|
174
|
+
editable: true,
|
|
175
|
+
attachmentCount: 2,
|
|
176
|
+
attachmentState: 'attached',
|
|
177
|
+
}, taskTitle: null, onDeleted: onDeleted }));
|
|
178
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-attached/content', { credentials: 'same-origin' }, ''));
|
|
179
|
+
await user.click(screen.getByRole('button', { name: /Delete & Unlink/i }));
|
|
180
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attached', expect.objectContaining({
|
|
181
|
+
method: 'DELETE',
|
|
182
|
+
credentials: 'include',
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ forceDetach: true })
|
|
185
|
+
}), ''));
|
|
186
|
+
expect(confirmSpy).toHaveBeenCalledWith(expect.stringContaining('remove it from 2 linked tasks'));
|
|
187
|
+
expect(onDeleted).toHaveBeenCalled();
|
|
188
|
+
confirmSpy.mockRestore();
|
|
189
|
+
alertSpy.mockRestore();
|
|
190
|
+
});
|
|
145
191
|
it('uses same-origin document routes in local runtime even when an api base is configured', async () => {
|
|
146
192
|
const user = userEvent.setup();
|
|
147
193
|
fetchTaskforceApi
|
|
@@ -226,6 +272,46 @@ describe('Document workspace management', () => {
|
|
|
226
272
|
expect(await screen.findByText('Please tighten the rollout section.')).toBeInTheDocument();
|
|
227
273
|
expect(screen.getByText('Lines 18-24')).toBeInTheDocument();
|
|
228
274
|
});
|
|
275
|
+
it('toggles the review sidebar from the document header', async () => {
|
|
276
|
+
const user = userEvent.setup();
|
|
277
|
+
fetchTaskforceApi
|
|
278
|
+
.mockResolvedValueOnce({
|
|
279
|
+
ok: true,
|
|
280
|
+
text: async () => '# Example\n',
|
|
281
|
+
})
|
|
282
|
+
.mockResolvedValueOnce({
|
|
283
|
+
ok: true,
|
|
284
|
+
json: async () => ({
|
|
285
|
+
sessions: [{
|
|
286
|
+
id: 'review-1',
|
|
287
|
+
tenantId: 'default',
|
|
288
|
+
workspaceId: 'default',
|
|
289
|
+
assetId: 'asset-doc-1',
|
|
290
|
+
documentId: 'doc-1',
|
|
291
|
+
documentVersion: 1,
|
|
292
|
+
title: 'Review 1',
|
|
293
|
+
status: 'open',
|
|
294
|
+
createdByActorId: 'user',
|
|
295
|
+
updatedByActorId: 'user',
|
|
296
|
+
createdByActor: null,
|
|
297
|
+
updatedByActor: null,
|
|
298
|
+
createdAt: '2026-03-19T12:00:00.000Z',
|
|
299
|
+
updatedAt: '2026-03-19T12:05:00.000Z',
|
|
300
|
+
deletedAt: null,
|
|
301
|
+
comments: [],
|
|
302
|
+
}],
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
|
|
306
|
+
expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
|
|
307
|
+
expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
|
|
308
|
+
await user.click(screen.getByRole('button', { name: 'Hide Review' }));
|
|
309
|
+
expect(screen.queryByText('Review & Comments')).not.toBeInTheDocument();
|
|
310
|
+
expect(screen.queryByPlaceholderText('Add a comment for this document review…')).not.toBeInTheDocument();
|
|
311
|
+
await user.click(screen.getByRole('button', { name: 'Show Review' }));
|
|
312
|
+
expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
|
|
313
|
+
expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
|
|
314
|
+
});
|
|
229
315
|
it('creates a review session and posts comments from the document viewer', async () => {
|
|
230
316
|
const user = userEvent.setup();
|
|
231
317
|
fetchTaskforceApi
|
|
@@ -1087,13 +1173,114 @@ Body copy.
|
|
|
1087
1173
|
attachmentCount: 0,
|
|
1088
1174
|
},
|
|
1089
1175
|
];
|
|
1090
|
-
const { rerender } =
|
|
1176
|
+
const { rerender } = renderDocumentWorkspace({
|
|
1177
|
+
tasks: [{ id: 'task-1', title: 'Parent task' }],
|
|
1178
|
+
documents: docs.filter((doc) => doc.taskId),
|
|
1179
|
+
hasActiveFilters: true,
|
|
1180
|
+
});
|
|
1091
1181
|
expect(screen.getByText('Attached fallback')).toBeInTheDocument();
|
|
1092
1182
|
expect(screen.queryByText('Unattached fallback')).not.toBeInTheDocument();
|
|
1093
|
-
rerender(_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs, loading: false, error: null, selectedDoc: null, searchQuery: "",
|
|
1183
|
+
rerender(_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => !doc.taskId), loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
1094
1184
|
expect(screen.getByText('Unattached fallback')).toBeInTheDocument();
|
|
1095
1185
|
expect(screen.queryByText('Attached fallback')).not.toBeInTheDocument();
|
|
1096
1186
|
});
|
|
1187
|
+
it('switches the active document when a different index item is selected', async () => {
|
|
1188
|
+
const user = userEvent.setup();
|
|
1189
|
+
const docs = [
|
|
1190
|
+
createDoc({
|
|
1191
|
+
id: 'doc-first',
|
|
1192
|
+
assetId: 'asset-doc-first',
|
|
1193
|
+
apiUrl: '/api/taskforce/documents/asset-doc-first/content',
|
|
1194
|
+
displayName: 'First notes',
|
|
1195
|
+
title: 'First notes',
|
|
1196
|
+
}),
|
|
1197
|
+
createDoc({
|
|
1198
|
+
id: 'doc-second',
|
|
1199
|
+
assetId: 'asset-doc-second',
|
|
1200
|
+
apiUrl: '/api/taskforce/documents/asset-doc-second/content',
|
|
1201
|
+
displayName: 'Second notes',
|
|
1202
|
+
title: 'Second notes',
|
|
1203
|
+
fsPath: 'workspaces/default/assets/documents/second-notes.md',
|
|
1204
|
+
}),
|
|
1205
|
+
];
|
|
1206
|
+
fetchTaskforceApi
|
|
1207
|
+
.mockResolvedValueOnce({
|
|
1208
|
+
ok: true,
|
|
1209
|
+
json: async () => ({ documents: docs }),
|
|
1210
|
+
})
|
|
1211
|
+
.mockResolvedValueOnce({
|
|
1212
|
+
ok: true,
|
|
1213
|
+
text: async () => '# First content\n',
|
|
1214
|
+
})
|
|
1215
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1216
|
+
.mockResolvedValueOnce({
|
|
1217
|
+
ok: true,
|
|
1218
|
+
text: async () => '# Second content\n',
|
|
1219
|
+
})
|
|
1220
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse());
|
|
1221
|
+
render(_jsx(DocumentWorkspaceShell, { tasks: [], runtimeMode: "local" }));
|
|
1222
|
+
expect(await screen.findByText('First content')).toBeInTheDocument();
|
|
1223
|
+
await user.click(screen.getByRole('button', { name: /Second notes/i }));
|
|
1224
|
+
expect(await screen.findByText('Second content')).toBeInTheDocument();
|
|
1225
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/asset-doc-second/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
|
|
1226
|
+
});
|
|
1227
|
+
it('returns to the remaining filtered document when the active selection is filtered out', async () => {
|
|
1228
|
+
const user = userEvent.setup();
|
|
1229
|
+
const docs = [
|
|
1230
|
+
createDoc({
|
|
1231
|
+
id: 'doc-attached-return',
|
|
1232
|
+
assetId: 'asset-doc-attached-return',
|
|
1233
|
+
apiUrl: '/api/taskforce/documents/asset-doc-attached-return/content',
|
|
1234
|
+
displayName: 'Attached return doc',
|
|
1235
|
+
title: 'Attached return doc',
|
|
1236
|
+
attachmentCount: 1,
|
|
1237
|
+
attachmentState: 'attached',
|
|
1238
|
+
taskId: 'task-1',
|
|
1239
|
+
}),
|
|
1240
|
+
createDoc({
|
|
1241
|
+
id: 'doc-unattached-return',
|
|
1242
|
+
assetId: 'asset-doc-unattached-return',
|
|
1243
|
+
apiUrl: '/api/taskforce/documents/asset-doc-unattached-return/content',
|
|
1244
|
+
displayName: 'Unattached return doc',
|
|
1245
|
+
title: 'Unattached return doc',
|
|
1246
|
+
fsPath: 'workspaces/default/assets/documents/unattached-return-doc.md',
|
|
1247
|
+
attachmentCount: 0,
|
|
1248
|
+
attachmentState: 'unattached',
|
|
1249
|
+
taskId: null,
|
|
1250
|
+
}),
|
|
1251
|
+
];
|
|
1252
|
+
function Harness() {
|
|
1253
|
+
const [attachmentFilters, setAttachmentFilters] = useState([]);
|
|
1254
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => setAttachmentFilters(['attached']), children: "Show attached only" }), _jsx(DocumentWorkspaceShell, { tasks: [{ id: 'task-1', title: 'Parent task' }], runtimeMode: "local", attachmentFilters: attachmentFilters })] }));
|
|
1255
|
+
}
|
|
1256
|
+
fetchTaskforceApi
|
|
1257
|
+
.mockResolvedValueOnce({
|
|
1258
|
+
ok: true,
|
|
1259
|
+
json: async () => ({ documents: docs }),
|
|
1260
|
+
})
|
|
1261
|
+
.mockResolvedValueOnce({
|
|
1262
|
+
ok: true,
|
|
1263
|
+
text: async () => '# Attached return content\n',
|
|
1264
|
+
})
|
|
1265
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1266
|
+
.mockResolvedValueOnce({
|
|
1267
|
+
ok: true,
|
|
1268
|
+
text: async () => '# Unattached return content\n',
|
|
1269
|
+
})
|
|
1270
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1271
|
+
.mockResolvedValueOnce({
|
|
1272
|
+
ok: true,
|
|
1273
|
+
text: async () => '# Attached return content\n',
|
|
1274
|
+
})
|
|
1275
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse());
|
|
1276
|
+
render(_jsx(Harness, {}));
|
|
1277
|
+
expect(await screen.findByText('Attached return content')).toBeInTheDocument();
|
|
1278
|
+
await user.click(screen.getByRole('button', { name: /Unattached return doc/i }));
|
|
1279
|
+
expect(await screen.findByText('Unattached return content')).toBeInTheDocument();
|
|
1280
|
+
await user.click(screen.getByRole('button', { name: /Show attached only/i }));
|
|
1281
|
+
expect(await screen.findByText('Attached return content')).toBeInTheDocument();
|
|
1282
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(6, '/api/taskforce/documents/asset-doc-attached-return/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
|
|
1283
|
+
});
|
|
1097
1284
|
it('selects a requested document by asset id when normalized paths differ', async () => {
|
|
1098
1285
|
const docs = [
|
|
1099
1286
|
{
|
|
@@ -3,7 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { Sun, Moon, Sparkles, Keyboard, FileText, Check, AlertTriangle, Loader2, Settings, Layers, Box, Copy, Trash2, Terminal, Bot, Codepen, Command, Wind, Github, Zap, ChevronDown, ChevronRight, Plug } from 'lucide-react';
|
|
4
4
|
import styles from '../../Taskforce.module.css';
|
|
5
5
|
import { TASKFORCE_THEME_OPTIONS } from '../../utils/theme';
|
|
6
|
-
import { MCP_CLIENT_OPTIONS, buildCloudMcpClientIntegration, buildCloudMcpServerId, sanitizeMcpId } from '../../mcp/clientIntegrations';
|
|
6
|
+
import { MCP_CLIENT_OPTIONS, buildCloudMcpClientIntegration, buildLocalMcpClientIntegration, buildCloudMcpServerId, sanitizeMcpId } from '../../mcp/clientIntegrations';
|
|
7
7
|
import { resolveTaskforceMcpBaseUrlFromBaseUrl } from '../../config/cloudEnvironment';
|
|
8
8
|
import { TaxonomyManager } from './settings/TaxonomyManager';
|
|
9
9
|
const THEME_ICON_MAP = {
|
|
@@ -12,6 +12,7 @@ const THEME_ICON_MAP = {
|
|
|
12
12
|
moon: Moon,
|
|
13
13
|
sparkles: Sparkles,
|
|
14
14
|
};
|
|
15
|
+
const DEFAULT_MCP_TOKEN_SCOPES = ['tasks:read', 'tasks:write', 'workspace:read'];
|
|
15
16
|
export function TaskSettings(props) {
|
|
16
17
|
const { settingsModel, onSectionChange } = props;
|
|
17
18
|
const { currentTheme, globalTheme, themeUseGlobalDefault, keyShortcut, jsonBackupEnabled, globalJsonBackupEnabled, globalWeekStartsOn, locale = 'en-US', supportedLocales = ['en-US', 'es-419', 'pt-BR'], jsonBackupUseGlobalDefault, manualComplexityEnabled, checklistDropdownEnabled = true, showTaskCardStatusLabel = true, exportWorkflowsPath, exportingResource, exportResult, initialSection, onThemeChange, onSaveTheme, onSaveGlobalTheme, onResetProjectToGlobal, onKeyShortcutChange, onJsonBackupEnabledChange, onSaveGlobalJsonBackupEnabled, onSaveGlobalWeekStartsOn, onSaveLocale, onManualComplexityEnabledChange, onChecklistDropdownEnabledChange, onShowTaskCardStatusLabelChange, onSaveSettings, onExportWorkflowsPathChange, onShowFolderBrowserChange, onBrowserTargetChange, onFetchFolders, categories, pathValidation, taxonomyDisplayLabels, onUpdateCategory, onRemoveCategory, onSaveCategory, onAddPath, onRemovePath, onUpdateCategoryIcon, onUpdateCategoryColor, types, onSaveType, onRemoveType, onUpdateType, taxonomies, onUpdateTaxonomies, priorities, onUpdatePriorities, onAnalyzeSystemTaxonomyPack, onApplySystemTaxonomyPack, onUpdateTaxonomyDisplayLabels, projectRoot, projectName, mcpHostRoot, serverHostRoot, mcpScriptPath, tenantId, workspaceId, runtimeMode = 'local', cloudAuthBaseUrl = '', cloudMcpBaseUrl = '', fetchCloudAuthApi, workspaceSwitchingEnabled = false, currentWorkspaceRole = 'member', currentWorkspaceName = '', onDeleteWorkspace, onMcpHostRootChange, isAuthenticated = false, exportEnvironment, setupState = null, buildInfo = null, onExportEnvironmentChange, onSaveSetupMode, onSaveWorkspaceProfile, availableWorkflows = [], onExportWorkflows, initiativeTemplates = [], onRefreshInitiativeTemplates, onCreateInitiativeFromTemplate, onRefreshWorkflows } = settingsModel;
|
|
@@ -147,7 +148,7 @@ export function TaskSettings(props) {
|
|
|
147
148
|
const cloudMcpAvailable = runtimeMode === 'cloud' || isAuthenticated;
|
|
148
149
|
const defaultMcpMode = runtimeMode === 'cloud'
|
|
149
150
|
? 'cloud'
|
|
150
|
-
:
|
|
151
|
+
: 'local';
|
|
151
152
|
const [selectedMcpMode, setSelectedMcpMode] = useState(defaultMcpMode);
|
|
152
153
|
const [copiedConfig, setCopiedConfig] = useState(false);
|
|
153
154
|
const [useGlobalMcp, setUseGlobalMcp] = useState(true);
|
|
@@ -167,11 +168,10 @@ export function TaskSettings(props) {
|
|
|
167
168
|
return `${label} MCP`;
|
|
168
169
|
}, [currentWorkspaceName, setupState?.workspace?.name, projectName, workspaceId]);
|
|
169
170
|
const [newTokenName, setNewTokenName] = useState(defaultTokenName);
|
|
170
|
-
const [newTokenScopes, setNewTokenScopes] = useState(['tasks:read', 'tasks:write', 'workspace:read']);
|
|
171
171
|
const [newTokenExpiresAt, setNewTokenExpiresAt] = useState('');
|
|
172
172
|
const [mcpTokenNotice, setMcpTokenNotice] = useState(null);
|
|
173
173
|
const [generatedTokenValue, setGeneratedTokenValue] = useState(null);
|
|
174
|
-
const [selectedMcpClient, setSelectedMcpClient] = useState('
|
|
174
|
+
const [selectedMcpClient, setSelectedMcpClient] = useState('general');
|
|
175
175
|
const [mcpConnectionToken, setMcpConnectionToken] = useState('');
|
|
176
176
|
const [mcpConnectionBusy, setMcpConnectionBusy] = useState(false);
|
|
177
177
|
const [mcpConnectionResult, setMcpConnectionResult] = useState(null);
|
|
@@ -282,16 +282,6 @@ export function TaskSettings(props) {
|
|
|
282
282
|
const segments = String(text || '').split(tokenText);
|
|
283
283
|
return segments.map((segment, index) => (_jsxs(React.Fragment, { children: [_jsx("span", { style: normalText, children: segment }), index < segments.length - 1 && _jsx("span", { style: highlighted, children: tokenText })] }, `text-config-${index}`)));
|
|
284
284
|
};
|
|
285
|
-
const toggleTokenScope = (scope) => {
|
|
286
|
-
setNewTokenScopes((current) => {
|
|
287
|
-
if (current.includes(scope)) {
|
|
288
|
-
if (current.length === 1)
|
|
289
|
-
return current;
|
|
290
|
-
return current.filter((item) => item !== scope);
|
|
291
|
-
}
|
|
292
|
-
return [...current, scope];
|
|
293
|
-
});
|
|
294
|
-
};
|
|
295
285
|
const copyToClipboard = (value, mode) => {
|
|
296
286
|
navigator.clipboard.writeText(value);
|
|
297
287
|
setCopiedConfig(mode);
|
|
@@ -351,13 +341,45 @@ export function TaskSettings(props) {
|
|
|
351
341
|
}
|
|
352
342
|
return { serverId, serverConfig };
|
|
353
343
|
};
|
|
344
|
+
const localMcpServer = useMemo(() => buildMcpServerConfig(), [
|
|
345
|
+
mcpScriptPath,
|
|
346
|
+
projectRoot,
|
|
347
|
+
mcpHostRoot,
|
|
348
|
+
serverHostRoot,
|
|
349
|
+
projectName,
|
|
350
|
+
tenantId,
|
|
351
|
+
workspaceId,
|
|
352
|
+
setupState?.workspaceId,
|
|
353
|
+
useGlobalMcp
|
|
354
|
+
]);
|
|
355
|
+
const localClientIntegration = useMemo(() => buildLocalMcpClientIntegration({
|
|
356
|
+
clientId: selectedMcpClient,
|
|
357
|
+
serverId: localMcpServer.serverId,
|
|
358
|
+
serverConfig: localMcpServer.serverConfig
|
|
359
|
+
}), [localMcpServer, selectedMcpClient]);
|
|
360
|
+
const localMcpServerConfig = localClientIntegration.kind === 'json'
|
|
361
|
+
? localClientIntegration.serverConfig ?? null
|
|
362
|
+
: null;
|
|
363
|
+
const localMcpFullConfig = useMemo(() => {
|
|
364
|
+
if (localClientIntegration.kind === 'text') {
|
|
365
|
+
return localClientIntegration.renderedText || '';
|
|
366
|
+
}
|
|
367
|
+
let payload = {
|
|
368
|
+
[localClientIntegration.rootKey || 'mcpServers']: {
|
|
369
|
+
[localMcpServer.serverId]: localClientIntegration.serverConfig
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
for (const key of [...(localClientIntegration.wrapperKeys || [])].reverse()) {
|
|
373
|
+
payload = { [key]: payload };
|
|
374
|
+
}
|
|
375
|
+
return JSON.stringify(payload, null, 2);
|
|
376
|
+
}, [localClientIntegration, localMcpServer.serverId]);
|
|
354
377
|
const handleCopyConfig = (mode = 'full') => {
|
|
355
378
|
if (!mcpScriptPath && !useGlobalMcp)
|
|
356
379
|
return;
|
|
357
|
-
const { serverId, serverConfig } = buildMcpServerConfig();
|
|
358
380
|
const toCopy = mode === 'full'
|
|
359
|
-
?
|
|
360
|
-
: `"${serverId}": ${JSON.stringify(serverConfig, null, 2)}`;
|
|
381
|
+
? localMcpFullConfig
|
|
382
|
+
: `"${localMcpServer.serverId}": ${JSON.stringify(localMcpServer.serverConfig, null, 2)}`;
|
|
361
383
|
copyToClipboard(toCopy, mode);
|
|
362
384
|
};
|
|
363
385
|
const loadMcpTokens = async () => {
|
|
@@ -423,7 +445,7 @@ export function TaskSettings(props) {
|
|
|
423
445
|
headers: { 'Content-Type': 'application/json' },
|
|
424
446
|
body: JSON.stringify({
|
|
425
447
|
name: trimmedName,
|
|
426
|
-
scopes:
|
|
448
|
+
scopes: DEFAULT_MCP_TOKEN_SCOPES,
|
|
427
449
|
expiresAt: newTokenExpiresAt ? new Date(`${newTokenExpiresAt}T00:00:00`).toISOString() : null
|
|
428
450
|
})
|
|
429
451
|
});
|
|
@@ -500,7 +522,7 @@ export function TaskSettings(props) {
|
|
|
500
522
|
headers: { 'Content-Type': 'application/json' },
|
|
501
523
|
body: JSON.stringify({
|
|
502
524
|
token: trimmedToken,
|
|
503
|
-
requiredScopes:
|
|
525
|
+
requiredScopes: DEFAULT_MCP_TOKEN_SCOPES
|
|
504
526
|
})
|
|
505
527
|
});
|
|
506
528
|
const payload = await res.json().catch(() => ({}));
|
|
@@ -512,13 +534,36 @@ export function TaskSettings(props) {
|
|
|
512
534
|
const workspaceName = typeof details?.tokenWorkspaceId === 'string'
|
|
513
535
|
? String(details.tokenWorkspaceId)
|
|
514
536
|
: undefined;
|
|
515
|
-
const
|
|
537
|
+
const endpoint = typeof details?.endpoint === 'string'
|
|
538
|
+
? String(details.endpoint)
|
|
539
|
+
: cloudMcpEndpoint;
|
|
540
|
+
let message = payload?.code === 'MCP_TOKEN_MISSING_SCOPES' && missingScopes.length > 0
|
|
516
541
|
? `Token is missing required scopes: ${missingScopes.join(', ')}.`
|
|
517
542
|
: String(payload?.error || 'Failed to test MCP connection.');
|
|
543
|
+
let followup;
|
|
544
|
+
if (payload?.code === 'MCP_ENDPOINT_GATEWAY_ERROR') {
|
|
545
|
+
message = 'The dedicated MCP host is responding with a gateway error.';
|
|
546
|
+
followup = 'This usually means the cloud MCP service is degraded. Retry shortly, and if it keeps failing treat it as an environment incident rather than a bad token.';
|
|
547
|
+
}
|
|
548
|
+
else if (payload?.code === 'MCP_ENDPOINT_TIMEOUT') {
|
|
549
|
+
message = 'The dedicated MCP host timed out during MCP initialization.';
|
|
550
|
+
followup = 'This usually points to a degraded or overloaded cloud MCP service.';
|
|
551
|
+
}
|
|
552
|
+
else if (payload?.code === 'MCP_ENDPOINT_UNREACHABLE') {
|
|
553
|
+
message = 'The dedicated MCP host could not be reached.';
|
|
554
|
+
followup = 'Check the cloud MCP hostname and environment health before rotating credentials.';
|
|
555
|
+
}
|
|
556
|
+
else if (payload?.code === 'MCP_ENDPOINT_REJECTED_VALIDATED_TOKEN') {
|
|
557
|
+
message = 'The dedicated MCP host rejected a token that validated locally.';
|
|
558
|
+
followup = 'This suggests the MCP host is stale, routed incorrectly, or out of sync with auth data.';
|
|
559
|
+
}
|
|
518
560
|
setMcpConnectionResult({
|
|
519
561
|
type: 'error',
|
|
520
562
|
message,
|
|
521
563
|
workspaceName,
|
|
564
|
+
endpoint,
|
|
565
|
+
errorCode: typeof payload?.code === 'string' ? String(payload.code) : undefined,
|
|
566
|
+
followup,
|
|
522
567
|
});
|
|
523
568
|
return;
|
|
524
569
|
}
|
|
@@ -529,12 +574,15 @@ export function TaskSettings(props) {
|
|
|
529
574
|
scopes: Array.isArray(result?.scopes) ? result.scopes.map((scope) => String(scope)) : [],
|
|
530
575
|
workspaceName: typeof result?.workspaceName === 'string' ? String(result.workspaceName) : '',
|
|
531
576
|
workspaceId: typeof result?.workspaceId === 'string' ? String(result.workspaceId) : '',
|
|
577
|
+
endpoint: typeof result?.endpoint === 'string' ? String(result.endpoint) : cloudMcpEndpoint,
|
|
532
578
|
});
|
|
533
579
|
}
|
|
534
580
|
catch {
|
|
535
581
|
setMcpConnectionResult({
|
|
536
582
|
type: 'error',
|
|
537
|
-
message: 'Failed to test MCP connection.'
|
|
583
|
+
message: 'Failed to test MCP connection.',
|
|
584
|
+
endpoint: cloudMcpEndpoint,
|
|
585
|
+
followup: 'The settings page could not complete the connection probe. Check your network connection and the selected cloud environment.'
|
|
538
586
|
});
|
|
539
587
|
}
|
|
540
588
|
finally {
|
|
@@ -834,13 +882,13 @@ export function TaskSettings(props) {
|
|
|
834
882
|
});
|
|
835
883
|
}, initialTab: (initialSection === 'categories' || initialSection === 'types' || initialSection === 'priorities')
|
|
836
884
|
? initialSection
|
|
837
|
-
: undefined })] })), activeTab === 'mcp' && (_jsxs("div", { className: styles.settingsGroup, children: [showMcpModeToggle && (_jsxs("div", { className: styles.settingsTabs, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${selectedMcpMode === '
|
|
885
|
+
: undefined })] })), activeTab === 'mcp' && (_jsxs("div", { className: styles.settingsGroup, children: [showMcpModeToggle && (_jsxs("div", { className: styles.settingsTabs, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${selectedMcpMode === 'local' ? styles.settingsTabBtnActive : ''}`, onClick: () => setSelectedMcpMode('local'), children: "Local MCP" }), _jsx("button", { className: `${styles.settingsTabBtn} ${selectedMcpMode === 'cloud' ? styles.settingsTabBtnActive : ''}`, onClick: () => setSelectedMcpMode('cloud'), children: "Cloud MCP" })] })), showCloudMcp && (_jsx(_Fragment, { children: selectedMcpClientUsesOAuth ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.settingGroup, children: [_jsxs("div", { className: styles.settingLabelGroup, children: [_jsx("h3", { className: styles.settingTitle, children: "OAuth Connector Setup" }), _jsx("span", { className: styles.settingDescription, children: selectedMcpClient === 'chatgpt-desktop'
|
|
838
886
|
? 'ChatGPT Desktop uses the Taskforce OAuth connector flow instead of a personal MCP token.'
|
|
839
887
|
: 'Claude Desktop uses the Taskforce OAuth connector flow instead of a personal MCP token.' })] }), _jsx("div", { className: `${selectedConnectorGrant ? styles.successMessage : styles.settingHelper} ${styles.marginTop12}`, children: selectedConnectorGrant ? (_jsxs(_Fragment, { children: [_jsx(Check, { size: 14 }), _jsxs("div", { children: [_jsx("div", { children: "Connector already authorized for this workspace." }), _jsxs("div", { className: styles.settingHelper, children: ["Last used ", selectedConnectorGrant.lastUsedAt
|
|
840
888
|
? new Date(selectedConnectorGrant.lastUsedAt).toLocaleString()
|
|
841
|
-
: 'not yet'] })] })] })) : ('No active connector authorization for this workspace yet.') }), _jsx("div", { className: styles.settingDescription, style: { marginTop: '12px' }, children: "Use the server URL above in your client, then finish the OAuth authorization flow from the Integrations tab." }), _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', width: '100%', marginTop: '12px' }, children: _jsx("button", { className: styles.copyBtn, onClick: () => setActiveTab('integrations'), children: "Open Integrations" }) })] }), _jsxs("div", { className:
|
|
889
|
+
: 'not yet'] })] })] })) : ('No active connector authorization for this workspace yet.') }), _jsx("div", { className: styles.settingDescription, style: { marginTop: '12px' }, children: "Use the server URL above in your client, then finish the OAuth authorization flow from the Integrations tab." }), _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', width: '100%', marginTop: '12px' }, children: _jsx("button", { className: styles.copyBtn, onClick: () => setActiveTab('integrations'), children: "Open Integrations" }) })] }), _jsxs("div", { className: styles.settingGroup, children: [_jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.codeBlockWrapperCustom} ${styles.marginTop12}`, style: { minHeight: '260px' }, children: [_jsxs("div", { className: styles.codeHeader, children: [_jsx("span", { className: styles.codeLabel, children: "Cloud MCP Configuration" }), _jsxs("div", { className: styles.codeHeaderActions, children: [_jsx("select", { className: styles.settingInput, value: selectedMcpClient, onChange: (e) => setSelectedMcpClient(e.target.value), "aria-label": "MCP client", style: { width: 'auto', minWidth: '170px', padding: '6px 12px', fontSize: '12px', borderRadius: '8px' }, children: MCP_CLIENT_OPTIONS.map((option) => (_jsx("option", { value: option.id, disabled: option.disabled, children: option.label }, option.id))) }), _jsx("button", { className: `${styles.copyBtn} ${copiedConfig === 'full' ? styles.copyBtnActive : ''}`, onClick: handleCopyCloudMcpConfig, title: "Copy full configuration file", children: copiedConfig === 'full' ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }) })] })] }), _jsx("pre", { className: styles.codeBlock, children: _jsx("code", { children: cloudMcpServerConfig
|
|
842
890
|
? renderMcpConfigBlock(cloudMcpServerId, cloudMcpServerConfig, cloudClientIntegration.rootKey || 'mcpServers', cloudClientIntegration.wrapperKeys || [])
|
|
843
|
-
: renderTextConfigBlock(cloudClientIntegration.renderedText || '') }) })] }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.marginTop12}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("span", { className: styles.codeLabel, children: cloudClientIntegration.instructionLabel }) }), _jsx("div", { style: { padding: '16px 20px' }, children: _jsx("div", { className: styles.settingDescription, children: cloudClientIntegration.instructionText }) })] })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${styles.
|
|
891
|
+
: renderTextConfigBlock(cloudClientIntegration.renderedText || '') }) })] }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.marginTop12}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("span", { className: styles.codeLabel, children: cloudClientIntegration.instructionLabel }) }), _jsx("div", { style: { padding: '16px 20px' }, children: _jsx("div", { className: styles.settingDescription, children: cloudClientIntegration.instructionText }) })] })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.codeBlockWrapperCustom} ${styles.marginTop16}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("div", { className: styles.settingLabelGroup, style: { gap: '2px' }, children: _jsx("span", { className: styles.codeLabel, children: "Generate Token" }) }) }), _jsx("div", { className: styles.mcpTokenCardBody, children: _jsxs("div", { className: styles.mcpTokenFormGrid, children: [_jsxs("div", { className: styles.settingsItem, style: { gap: '6px' }, children: [_jsx("div", { className: styles.settingLabelGroup, children: _jsx("span", { className: styles.settingDescription, children: "Token Name" }) }), _jsx("input", { type: "text", className: styles.settingInput, placeholder: "Cursor on MacBook", value: newTokenName, onChange: (e) => setNewTokenName(e.target.value) })] }), _jsxs("div", { className: styles.settingsItem, style: { gap: '6px' }, children: [_jsx("div", { className: styles.settingLabelGroup, children: _jsx("span", { className: styles.settingDescription, children: "Expiry (Optional)" }) }), _jsx("input", { type: "date", className: `${styles.settingInput} ${styles.taskFormDateInput}`, value: newTokenExpiresAt, onChange: (e) => setNewTokenExpiresAt(e.target.value) })] })] }) }), (() => {
|
|
844
892
|
const tokenBarError = mcpTokenNotice?.type === 'error'
|
|
845
893
|
? mcpTokenNotice.message
|
|
846
894
|
: null;
|
|
@@ -848,20 +896,19 @@ export function TaskSettings(props) {
|
|
|
848
896
|
|| (copiedConfig === 'token'
|
|
849
897
|
? 'Copied to clipboard'
|
|
850
898
|
: (generatedTokenValue || ''));
|
|
851
|
-
return (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
textAlign: 'center',
|
|
899
|
+
return (_jsxs("div", { className: styles.mcpTokenOutputWrap, style: { cursor: generatedTokenValue ? 'pointer' : 'default' }, onClick: () => generatedTokenValue && copyToClipboard(generatedTokenValue, 'token'), role: generatedTokenValue ? 'button' : undefined, tabIndex: generatedTokenValue ? 0 : -1, onKeyDown: (e) => {
|
|
900
|
+
if (!generatedTokenValue)
|
|
901
|
+
return;
|
|
902
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
903
|
+
e.preventDefault();
|
|
904
|
+
copyToClipboard(generatedTokenValue, 'token');
|
|
905
|
+
}
|
|
906
|
+
}, title: generatedTokenValue ? (copiedConfig === 'token' ? 'Token copied' : 'Click to copy token') : undefined, children: [_jsx("div", { className: styles.settingLabelGroup, children: _jsx("span", { className: styles.settingDescription, children: "Token" }) }), _jsx("div", { className: styles.mcpTokenValueField, style: {
|
|
860
907
|
color: tokenBarError ? 'var(--status-danger-strong, #f87171)' : undefined,
|
|
861
|
-
}, children: _jsx("code", { children: displayedGeneratedToken }) }) }) }));
|
|
862
|
-
})()
|
|
908
|
+
}, children: _jsx("span", { className: styles.mcpTokenValueText, children: _jsx("code", { children: displayedGeneratedToken }) }) })] }));
|
|
909
|
+
})(), _jsx("div", { className: styles.mcpTokenActionRow, children: _jsxs("button", { className: styles.copyBtn, onClick: () => void handleCreateMcpToken(), disabled: mcpActionBusyId === 'create', title: "Generate Token", "aria-label": "Generate Token", children: [mcpActionBusyId === 'create' ? _jsx(Loader2, { size: 14, className: styles.spinIcon }) : _jsx(Zap, { size: 14 }), _jsx("span", { children: "GENERATE TOKEN" })] }) })] }), _jsxs("div", { className: styles.settingGroup, children: [_jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.codeBlockWrapperCustom} ${styles.marginTop12}`, style: { minHeight: '260px' }, children: [_jsxs("div", { className: styles.codeHeader, children: [_jsx("span", { className: styles.codeLabel, children: "Cloud MCP Configuration" }), _jsxs("div", { className: styles.codeHeaderActions, children: [_jsx("select", { className: styles.settingInput, value: selectedMcpClient, onChange: (e) => setSelectedMcpClient(e.target.value), "aria-label": "MCP client", style: { width: 'auto', minWidth: '170px', padding: '6px 12px', fontSize: '12px', borderRadius: '8px' }, children: MCP_CLIENT_OPTIONS.map((option) => (_jsx("option", { value: option.id, disabled: option.disabled, children: option.label }, option.id))) }), _jsx("button", { className: `${styles.copyBtn} ${copiedConfig === 'full' ? styles.copyBtnActive : ''}`, onClick: handleCopyCloudMcpConfig, title: "Copy full configuration file", children: copiedConfig === 'full' ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }) })] })] }), _jsx("pre", { className: styles.codeBlock, children: _jsx("code", { children: cloudMcpServerConfig
|
|
863
910
|
? renderMcpConfigBlock(cloudMcpServerId, cloudMcpServerConfig, cloudClientIntegration.rootKey || 'mcpServers', cloudClientIntegration.wrapperKeys || [])
|
|
864
|
-
: renderTextConfigBlock(cloudClientIntegration.renderedText || '') }) })] }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.marginTop12}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("span", { className: styles.codeLabel, children: cloudClientIntegration.instructionLabel }) }), _jsx("div", { style: { padding: '16px 20px' }, children: _jsx("div", { className: styles.settingDescription, children: cloudClientIntegration.instructionText }) })] })] }), _jsxs("div", { className:
|
|
911
|
+
: renderTextConfigBlock(cloudClientIntegration.renderedText || '') }) })] }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.marginTop12}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("span", { className: styles.codeLabel, children: cloudClientIntegration.instructionLabel }) }), _jsx("div", { style: { padding: '16px 20px' }, children: _jsx("div", { className: styles.settingDescription, children: cloudClientIntegration.instructionText }) })] })] }), _jsxs("div", { className: styles.settingGroup, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowMcpConnectionTest((prev) => !prev), onKeyDown: (e) => {
|
|
865
912
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
866
913
|
e.preventDefault();
|
|
867
914
|
setShowMcpConnectionTest((prev) => !prev);
|
|
@@ -872,7 +919,7 @@ export function TaskSettings(props) {
|
|
|
872
919
|
} })] }), _jsxs("div", { className: styles.marginTop12, style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'flex-end' }, children: [generatedTokenValue && (_jsx("button", { className: styles.copyBtn, onClick: () => {
|
|
873
920
|
setMcpConnectionToken(generatedTokenValue);
|
|
874
921
|
setMcpConnectionResult(null);
|
|
875
|
-
}, children: "Use Generated Token" })), _jsxs("button", { className: styles.copyBtn, onClick: () => void handleTestMcpConnection(), disabled: mcpConnectionBusy, children: [mcpConnectionBusy ? _jsx(Loader2, { size: 14, className: styles.spinIcon }) : _jsx(Zap, { size: 14 }), "Test Connection"] })] }), mcpConnectionResult && (_jsxs("div", { className: `${mcpConnectionResult.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [mcpConnectionResult.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), _jsxs("div", { children: [_jsx("div", { children: mcpConnectionResult.message }), mcpConnectionResult.type === 'success' && (mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId) && (_jsxs("div", { className: styles.settingHelper, children: ["Workspace: ", mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId, mcpConnectionResult.workspaceId ? ` (${mcpConnectionResult.workspaceId})` : ''] })), mcpConnectionResult.type === 'success' && Array.isArray(mcpConnectionResult.scopes) && mcpConnectionResult.scopes.length > 0 && (_jsxs("div", { className: styles.settingHelper, children: ["Granted scopes: ", mcpConnectionResult.scopes.join(', ')] }))] })] }))] }))] }), _jsxs("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowMcpTokensList((prev) => !prev), onKeyDown: (e) => {
|
|
922
|
+
}, children: "Use Generated Token" })), _jsxs("button", { className: styles.copyBtn, onClick: () => void handleTestMcpConnection(), disabled: mcpConnectionBusy, children: [mcpConnectionBusy ? _jsx(Loader2, { size: 14, className: styles.spinIcon }) : _jsx(Zap, { size: 14 }), "Test Connection"] })] }), mcpConnectionResult && (_jsxs("div", { className: `${mcpConnectionResult.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [mcpConnectionResult.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), _jsxs("div", { children: [_jsx("div", { children: mcpConnectionResult.message }), mcpConnectionResult.type === 'success' && (mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId) && (_jsxs("div", { className: styles.settingHelper, children: ["Workspace: ", mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId, mcpConnectionResult.workspaceId ? ` (${mcpConnectionResult.workspaceId})` : ''] })), mcpConnectionResult.type === 'success' && Array.isArray(mcpConnectionResult.scopes) && mcpConnectionResult.scopes.length > 0 && (_jsxs("div", { className: styles.settingHelper, children: ["Granted scopes: ", mcpConnectionResult.scopes.join(', ')] })), mcpConnectionResult.endpoint && (_jsxs("div", { className: styles.settingHelper, children: ["Endpoint: ", mcpConnectionResult.endpoint] })), mcpConnectionResult.type === 'error' && mcpConnectionResult.errorCode && (_jsxs("div", { className: styles.settingHelper, children: ["Error code: ", mcpConnectionResult.errorCode] })), mcpConnectionResult.type === 'error' && mcpConnectionResult.followup && (_jsx("div", { className: styles.settingHelper, children: mcpConnectionResult.followup }))] })] }))] }))] }), _jsxs("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowMcpTokensList((prev) => !prev), onKeyDown: (e) => {
|
|
876
923
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
877
924
|
e.preventDefault();
|
|
878
925
|
setShowMcpTokensList((prev) => !prev);
|
|
@@ -882,10 +929,9 @@ export function TaskSettings(props) {
|
|
|
882
929
|
borderRadius: '12px',
|
|
883
930
|
border: '1px solid var(--border-primary, rgba(255, 255, 255, 0.1))',
|
|
884
931
|
background: 'rgba(255, 255, 255, 0.03)'
|
|
885
|
-
}, children: [_jsxs("div", { className: styles.settingLabelGroup, children: [_jsxs("div", { className: styles.labelGroupFlex, children: [_jsx("label", { className: styles.settingLabel, children: token.name }), _jsx("span", { className: `${styles.configBadge} ${token.status === 'revoked' ? styles.configBadgeRevoked : token.status === 'expired' ? styles.configBadgeExpired : styles.configBadgeActive}`, children: token.status })] }), _jsx("span", { className: styles.settingDescription, style: { fontFamily: '"JetBrains Mono", "Fira Code", monospace' }, children: token.tokenPrefix }), _jsxs("span", { className: styles.settingDescription, children: ["Scopes: ", token.scopes.join(', ')] }), _jsxs("span", { className: styles.settingDescription, children: ["Created ", new Date(token.createdAt).toLocaleDateString(), token.lastUsedAt ? ` · Last used ${new Date(token.lastUsedAt).toLocaleString()}` : ' · Never used'] })] }), _jsxs("div", { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'flex-end' }, children: [token.status !== 'revoked' && (_jsx("button", { className: styles.copyBtn, onClick: () => void handleMcpTokenAction(token.id, 'regenerate'), disabled: mcpActionBusyId === `regenerate:${token.id}`, children: "Regenerate" })), token.status === 'active' && (_jsx("button", { className: styles.copyBtn, onClick: () => void handleMcpTokenAction(token.id, 'revoke'), disabled: mcpActionBusyId === `revoke:${token.id}`, children: "Revoke" }))] })] }, token.id))) }))] }))] })] })) })), showLocalMcp && (_jsxs(_Fragment, { children: [_jsx("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: _jsxs("div", { className: styles.settingLabelGroup, children: [_jsx("h3", { className: styles.settingTitle, children: "Local MCP" }), _jsx("span", { className: styles.settingDescription, children: "Local MCP runs over stdio and does not require a cloud token. Use this for Codex, Claude Code CLI, Gemini CLI, and similar local MCP clients." })] }) }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.codeBlockWrapperCustom}`, children: [_jsxs("div", { className: styles.codeHeader, children: [_jsx("span", { className: styles.codeLabel, children: "Local MCP Configuration" }),
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
})() }) })] }), _jsx("div", { className: `${styles.settingGroup} ${styles.marginTop12}`, children: _jsxs("div", { className: styles.toggleHeading, onClick: () => setShowAdvancedMcp((prev) => !prev), onKeyDown: (e) => {
|
|
932
|
+
}, children: [_jsxs("div", { className: styles.settingLabelGroup, children: [_jsxs("div", { className: styles.labelGroupFlex, children: [_jsx("label", { className: styles.settingLabel, children: token.name }), _jsx("span", { className: `${styles.configBadge} ${token.status === 'revoked' ? styles.configBadgeRevoked : token.status === 'expired' ? styles.configBadgeExpired : styles.configBadgeActive}`, children: token.status })] }), _jsx("span", { className: styles.settingDescription, style: { fontFamily: '"JetBrains Mono", "Fira Code", monospace' }, children: token.tokenPrefix }), _jsxs("span", { className: styles.settingDescription, children: ["Scopes: ", token.scopes.join(', ')] }), _jsxs("span", { className: styles.settingDescription, children: ["Created ", new Date(token.createdAt).toLocaleDateString(), token.lastUsedAt ? ` · Last used ${new Date(token.lastUsedAt).toLocaleString()}` : ' · Never used'] })] }), _jsxs("div", { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'flex-end' }, children: [token.status !== 'revoked' && (_jsx("button", { className: styles.copyBtn, onClick: () => void handleMcpTokenAction(token.id, 'regenerate'), disabled: mcpActionBusyId === `regenerate:${token.id}`, children: "Regenerate" })), token.status === 'active' && (_jsx("button", { className: styles.copyBtn, onClick: () => void handleMcpTokenAction(token.id, 'revoke'), disabled: mcpActionBusyId === `revoke:${token.id}`, children: "Revoke" }))] })] }, token.id))) }))] }))] })] })) })), showLocalMcp && (_jsxs(_Fragment, { children: [_jsx("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: _jsxs("div", { className: styles.settingLabelGroup, children: [_jsx("h3", { className: styles.settingTitle, children: "Local MCP" }), _jsx("span", { className: styles.settingDescription, children: "Local MCP runs over stdio and does not require a cloud token. Use this for Codex, Claude Code CLI, Gemini CLI, and similar local MCP clients." })] }) }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.codeBlockWrapperCustom}`, children: [_jsxs("div", { className: styles.codeHeader, children: [_jsx("span", { className: styles.codeLabel, children: "Local MCP Configuration" }), _jsxs("div", { className: styles.codeHeaderActions, children: [_jsx("select", { className: styles.settingInput, value: selectedMcpClient, onChange: (e) => setSelectedMcpClient(e.target.value), "aria-label": "MCP client", style: { width: 'auto', minWidth: '170px', padding: '6px 12px', fontSize: '12px', borderRadius: '8px' }, children: MCP_CLIENT_OPTIONS.map((option) => (_jsx("option", { value: option.id, disabled: option.disabled, children: option.label }, option.id))) }), _jsx("button", { className: `${styles.copyBtn} ${copiedConfig === 'full' ? styles.copyBtnActive : ''}`, onClick: () => handleCopyConfig('full'), title: "Copy full configuration file", children: copiedConfig === 'full' ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }) })] })] }), _jsx("pre", { className: styles.codeBlock, children: _jsx("code", { children: localMcpServerConfig
|
|
933
|
+
? renderMcpConfigBlock(localMcpServer.serverId, localMcpServerConfig, localClientIntegration.rootKey || 'mcpServers', localClientIntegration.wrapperKeys || [])
|
|
934
|
+
: renderTextConfigBlock(localClientIntegration.renderedText || '') }) })] }), _jsxs("div", { className: `${styles.codeBlockWrapper} ${styles.marginTop12}`, children: [_jsx("div", { className: styles.codeHeader, children: _jsx("span", { className: styles.codeLabel, children: localClientIntegration.instructionLabel }) }), _jsx("div", { style: { padding: '16px 20px' }, children: _jsx("div", { className: styles.settingDescription, children: localClientIntegration.instructionText }) })] }), _jsx("div", { className: `${styles.settingGroup} ${styles.marginTop12}`, children: _jsxs("div", { className: styles.toggleHeading, onClick: () => setShowAdvancedMcp((prev) => !prev), onKeyDown: (e) => {
|
|
889
935
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
890
936
|
e.preventDefault();
|
|
891
937
|
setShowAdvancedMcp((prev) => !prev);
|