@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
package/dist/mcp/runtime.test.js
CHANGED
|
@@ -436,11 +436,11 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
436
436
|
expect(linkTaskContext.description).toContain('external http(s) URL');
|
|
437
437
|
expect(saveTaskAttachment.description).toContain('generated content');
|
|
438
438
|
expect(saveTaskAttachment.description).toContain('overwrite=true');
|
|
439
|
-
expect(saveTaskAttachment.description).toContain('AI-authored markdown/text
|
|
439
|
+
expect(saveTaskAttachment.description).toContain('use inline content for AI-authored markdown/text');
|
|
440
440
|
expect(saveTaskAttachment.inputSchema).toBeDefined();
|
|
441
441
|
const saveTaskAttachmentSchema = saveTaskAttachment.inputSchema;
|
|
442
442
|
expect(saveTaskAttachmentSchema.properties.content.description).toContain('use this for AI-authored markdown/text attachments');
|
|
443
|
-
expect(saveTaskAttachmentSchema.properties.sourcePath.description).toContain('
|
|
443
|
+
expect(saveTaskAttachmentSchema.properties.sourcePath.description).toContain('approved agent workspace root');
|
|
444
444
|
expect(resolveProfile.description).toContain('AI collaborator profile');
|
|
445
445
|
expect(resolveProfile.description).toContain('Update semantics');
|
|
446
446
|
expect(resolveProfile.description).toContain('empty strings are ignored rather than clearing');
|
|
@@ -733,6 +733,56 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
733
733
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
734
734
|
}
|
|
735
735
|
});
|
|
736
|
+
it('updates and persists metadata when cloud auto-bind reuses an existing exact-match profile', async () => {
|
|
737
|
+
const projectRoot = createProjectRoot();
|
|
738
|
+
try {
|
|
739
|
+
const initial = parseToolJson(await callTool(projectRoot, 'local', 'resolve_profile', {
|
|
740
|
+
name: 'Automation Codex',
|
|
741
|
+
icon: 'Bot',
|
|
742
|
+
color: '#64748b'
|
|
743
|
+
}));
|
|
744
|
+
expect(initial.profile.description).toBeNull();
|
|
745
|
+
expect(initial.profile.role).toBeNull();
|
|
746
|
+
const currentProfile = parseToolJson(await callTool(projectRoot, 'cloud', 'get_current_profile', {}, {
|
|
747
|
+
workspaceId: 'workspace-1',
|
|
748
|
+
resolveProfile: false,
|
|
749
|
+
authAiProfileName: 'Automation Codex',
|
|
750
|
+
authAiProfileDescription: 'OpenAI Codex coding agent connected through MCP.',
|
|
751
|
+
authAiProfileRole: 'Coding Agent',
|
|
752
|
+
authAiProfileProvider: 'OpenAI',
|
|
753
|
+
authAiProfileModel: 'Codex',
|
|
754
|
+
authAiProfileSurfaceType: 'coding_tool'
|
|
755
|
+
}));
|
|
756
|
+
expect(currentProfile.profile).toEqual(expect.objectContaining({
|
|
757
|
+
id: initial.profile.id,
|
|
758
|
+
name: 'Automation Codex',
|
|
759
|
+
description: 'OpenAI Codex coding agent connected through MCP.',
|
|
760
|
+
role: 'Coding Agent',
|
|
761
|
+
provider: 'OpenAI',
|
|
762
|
+
model: 'Codex',
|
|
763
|
+
surfaceType: 'coding_tool'
|
|
764
|
+
}));
|
|
765
|
+
const reloadedCore = new TaskforceCore({
|
|
766
|
+
projectRoot,
|
|
767
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
768
|
+
});
|
|
769
|
+
expect(reloadedCore.listAiProfiles('workspace-1')).toEqual([
|
|
770
|
+
expect.objectContaining({
|
|
771
|
+
id: initial.profile.id,
|
|
772
|
+
name: 'Automation Codex',
|
|
773
|
+
description: 'OpenAI Codex coding agent connected through MCP.',
|
|
774
|
+
role: 'Coding Agent',
|
|
775
|
+
provider: 'OpenAI',
|
|
776
|
+
model: 'Codex',
|
|
777
|
+
surfaceType: 'coding_tool'
|
|
778
|
+
})
|
|
779
|
+
]);
|
|
780
|
+
reloadedCore.close();
|
|
781
|
+
}
|
|
782
|
+
finally {
|
|
783
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
784
|
+
}
|
|
785
|
+
});
|
|
736
786
|
it('allows mutating MCP tools in a fresh runtime when profileToken is passed', async () => {
|
|
737
787
|
const projectRoot = createProjectRoot();
|
|
738
788
|
try {
|
|
@@ -816,6 +866,60 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
816
866
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
817
867
|
}
|
|
818
868
|
});
|
|
869
|
+
it('blocks cloud auto-bind when the workspace has no remaining ai profile seats', async () => {
|
|
870
|
+
const projectRoot = createProjectRoot();
|
|
871
|
+
try {
|
|
872
|
+
const core = new TaskforceCore({
|
|
873
|
+
projectRoot,
|
|
874
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
875
|
+
});
|
|
876
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
877
|
+
core.ensureBootstrapUserAccess({
|
|
878
|
+
userId: 'owner-u1',
|
|
879
|
+
workspaceId: 'workspace-1',
|
|
880
|
+
role: 'owner',
|
|
881
|
+
email: 'owner@example.com'
|
|
882
|
+
});
|
|
883
|
+
core.updatePlanVersionFeatures({
|
|
884
|
+
planVersionId: 'free-v1',
|
|
885
|
+
features: [
|
|
886
|
+
{ featureKey: 'collaboration.ai_profiles', access: 'limited', config: { maxAiProfiles: 1 } }
|
|
887
|
+
]
|
|
888
|
+
});
|
|
889
|
+
core.updateAccountEntitlement({
|
|
890
|
+
accountId: 'owner-u1',
|
|
891
|
+
planVersionId: 'free-v1',
|
|
892
|
+
entitlementState: 'active',
|
|
893
|
+
actorUserId: 'system-admin',
|
|
894
|
+
actorRole: 'system_admin'
|
|
895
|
+
});
|
|
896
|
+
core.resolveAiProfile({
|
|
897
|
+
workspaceId: 'workspace-1',
|
|
898
|
+
name: 'Existing Automation Bot'
|
|
899
|
+
});
|
|
900
|
+
core.close();
|
|
901
|
+
const result = await callTool(projectRoot, 'cloud', 'create_task', {
|
|
902
|
+
title: 'Blocked automation write'
|
|
903
|
+
}, {
|
|
904
|
+
workspaceId: 'workspace-1',
|
|
905
|
+
resolveProfile: false,
|
|
906
|
+
authAiProfileName: 'Automation Codex'
|
|
907
|
+
});
|
|
908
|
+
expect(result.isError).toBe(true);
|
|
909
|
+
const payload = parseToolJson(result);
|
|
910
|
+
expect(payload.code).toBe('AI_PROFILE_LIMIT_REACHED');
|
|
911
|
+
expect(payload.error).toContain('No AI seats are available for this workspace');
|
|
912
|
+
expect(payload.details).toEqual({
|
|
913
|
+
workspaceId: 'workspace-1',
|
|
914
|
+
limit: 1,
|
|
915
|
+
used: 1,
|
|
916
|
+
remaining: 0
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
finally {
|
|
920
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
921
|
+
}
|
|
922
|
+
});
|
|
819
923
|
it('rejects invalid profileToken values on mutating MCP tools', async () => {
|
|
820
924
|
const projectRoot = createProjectRoot();
|
|
821
925
|
try {
|
|
@@ -876,6 +980,61 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
876
980
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
877
981
|
}
|
|
878
982
|
});
|
|
983
|
+
it('surfaces ai profile seat limits when an invalid per-call token falls back to a configured AI name', async () => {
|
|
984
|
+
const projectRoot = createProjectRoot();
|
|
985
|
+
try {
|
|
986
|
+
const core = new TaskforceCore({
|
|
987
|
+
projectRoot,
|
|
988
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
989
|
+
});
|
|
990
|
+
core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
|
|
991
|
+
core.ensureBootstrapUserAccess({
|
|
992
|
+
userId: 'owner-u1',
|
|
993
|
+
workspaceId: 'workspace-1',
|
|
994
|
+
role: 'owner',
|
|
995
|
+
email: 'owner@example.com'
|
|
996
|
+
});
|
|
997
|
+
core.updatePlanVersionFeatures({
|
|
998
|
+
planVersionId: 'free-v1',
|
|
999
|
+
features: [
|
|
1000
|
+
{ featureKey: 'collaboration.ai_profiles', access: 'limited', config: { maxAiProfiles: 1 } }
|
|
1001
|
+
]
|
|
1002
|
+
});
|
|
1003
|
+
core.updateAccountEntitlement({
|
|
1004
|
+
accountId: 'owner-u1',
|
|
1005
|
+
planVersionId: 'free-v1',
|
|
1006
|
+
entitlementState: 'active',
|
|
1007
|
+
actorUserId: 'system-admin',
|
|
1008
|
+
actorRole: 'system_admin'
|
|
1009
|
+
});
|
|
1010
|
+
core.resolveAiProfile({
|
|
1011
|
+
workspaceId: 'workspace-1',
|
|
1012
|
+
name: 'Existing Automation Bot'
|
|
1013
|
+
});
|
|
1014
|
+
core.close();
|
|
1015
|
+
const result = await callTool(projectRoot, 'cloud', 'create_task', {
|
|
1016
|
+
title: 'Recovered per-call token write',
|
|
1017
|
+
profileToken: 'tfp_invalid_token',
|
|
1018
|
+
}, {
|
|
1019
|
+
workspaceId: 'workspace-1',
|
|
1020
|
+
resolveProfile: false,
|
|
1021
|
+
authAiProfileName: 'Automation Codex'
|
|
1022
|
+
});
|
|
1023
|
+
expect(result.isError).toBe(true);
|
|
1024
|
+
const payload = parseToolJson(result);
|
|
1025
|
+
expect(payload.code).toBe('AI_PROFILE_LIMIT_REACHED');
|
|
1026
|
+
expect(payload.error).toContain('No AI seats are available for this workspace');
|
|
1027
|
+
expect(payload.details).toEqual({
|
|
1028
|
+
workspaceId: 'workspace-1',
|
|
1029
|
+
limit: 1,
|
|
1030
|
+
used: 1,
|
|
1031
|
+
remaining: 0
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
1035
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
879
1038
|
it('claims unassigned tasks and marks them in progress through start_task', async () => {
|
|
880
1039
|
const projectRoot = createProjectRoot();
|
|
881
1040
|
try {
|
|
@@ -1667,6 +1826,40 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1667
1826
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1668
1827
|
}
|
|
1669
1828
|
});
|
|
1829
|
+
it('returns summary-only rows from list_tasks json output', async () => {
|
|
1830
|
+
const projectRoot = createProjectRoot();
|
|
1831
|
+
try {
|
|
1832
|
+
const created = parseToolJson(await callTool(projectRoot, 'local', 'create_task', {
|
|
1833
|
+
title: 'List summary task',
|
|
1834
|
+
description: 'Verbose description that should not appear in list rows'
|
|
1835
|
+
}));
|
|
1836
|
+
await callTool(projectRoot, 'local', 'replace_task_checklist', {
|
|
1837
|
+
taskId: created.referenceLabel,
|
|
1838
|
+
items: [
|
|
1839
|
+
{ title: 'Checklist item one', isCompleted: false }
|
|
1840
|
+
]
|
|
1841
|
+
});
|
|
1842
|
+
await callTool(projectRoot, 'local', 'add_comment', {
|
|
1843
|
+
id: created.referenceLabel,
|
|
1844
|
+
text: 'Verbose comment that should not appear in list rows',
|
|
1845
|
+
author: 'ai-profile-019d15c5-6ad3-7bf0-9b9e-fa9c366c3ff4'
|
|
1846
|
+
});
|
|
1847
|
+
const listed = parseToolJson(await callTool(projectRoot, 'local', 'list_tasks', {
|
|
1848
|
+
format: 'json'
|
|
1849
|
+
}));
|
|
1850
|
+
const row = listed.find((task) => task.referenceLabel === created.referenceLabel);
|
|
1851
|
+
expect(row).toBeTruthy();
|
|
1852
|
+
expect(row.description).toBeUndefined();
|
|
1853
|
+
expect(row.comments).toBeUndefined();
|
|
1854
|
+
expect(row.taskEvents).toBeUndefined();
|
|
1855
|
+
expect(row.activity).toBeUndefined();
|
|
1856
|
+
expect(row.attachments).toBeUndefined();
|
|
1857
|
+
expect(row.checklistItems).toBeUndefined();
|
|
1858
|
+
}
|
|
1859
|
+
finally {
|
|
1860
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1670
1863
|
it('reads task resources by T-XXX reference and exposes version/scope in config resource', async () => {
|
|
1671
1864
|
const projectRoot = createProjectRoot();
|
|
1672
1865
|
try {
|
|
@@ -1961,8 +2154,45 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1961
2154
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1962
2155
|
}
|
|
1963
2156
|
});
|
|
1964
|
-
it('
|
|
2157
|
+
it('allows save_task_attachment sourcePath in cloud runtime for approved agent workspace roots', async () => {
|
|
1965
2158
|
const projectRoot = createProjectRoot();
|
|
2159
|
+
const agentWorkspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-mcp-agent-workspace-'));
|
|
2160
|
+
const previousRoots = process.env.TASKFORCE_ALLOWED_ATTACHMENT_SOURCE_ROOTS;
|
|
2161
|
+
try {
|
|
2162
|
+
const core = new TaskforceCore({
|
|
2163
|
+
projectRoot,
|
|
2164
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
2165
|
+
});
|
|
2166
|
+
const task = core.createTask({ title: 'Cloud sourcePath allow test' }, { workspaceId: 'workspace-1' });
|
|
2167
|
+
core.close();
|
|
2168
|
+
const sourcePath = path.join(agentWorkspaceRoot, 'implementation-plan.md');
|
|
2169
|
+
fs.writeFileSync(sourcePath, '# Recovered implementation plan', 'utf8');
|
|
2170
|
+
process.env.TASKFORCE_ALLOWED_ATTACHMENT_SOURCE_ROOTS = agentWorkspaceRoot;
|
|
2171
|
+
await callTool(projectRoot, 'cloud', 'save_task_attachment', {
|
|
2172
|
+
id: task.id,
|
|
2173
|
+
filename: 'implementation-plan.md',
|
|
2174
|
+
sourcePath
|
|
2175
|
+
});
|
|
2176
|
+
const readBack = parseToolJson(await callTool(projectRoot, 'cloud', 'read_task_attachment', {
|
|
2177
|
+
taskId: task.id,
|
|
2178
|
+
attachmentId: parseToolJson(await callTool(projectRoot, 'cloud', 'get_task_attachments', { id: task.id })).attachments[0]?.attachmentId
|
|
2179
|
+
}));
|
|
2180
|
+
expect(readBack.content).toContain('# Recovered implementation plan');
|
|
2181
|
+
}
|
|
2182
|
+
finally {
|
|
2183
|
+
if (previousRoots === undefined) {
|
|
2184
|
+
delete process.env.TASKFORCE_ALLOWED_ATTACHMENT_SOURCE_ROOTS;
|
|
2185
|
+
}
|
|
2186
|
+
else {
|
|
2187
|
+
process.env.TASKFORCE_ALLOWED_ATTACHMENT_SOURCE_ROOTS = previousRoots;
|
|
2188
|
+
}
|
|
2189
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
2190
|
+
fs.rmSync(agentWorkspaceRoot, { recursive: true, force: true });
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
it('rejects save_task_attachment sourcePath in cloud runtime for unapproved roots', async () => {
|
|
2194
|
+
const projectRoot = createProjectRoot();
|
|
2195
|
+
const externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-mcp-cloud-unapproved-'));
|
|
1966
2196
|
try {
|
|
1967
2197
|
const core = new TaskforceCore({
|
|
1968
2198
|
projectRoot,
|
|
@@ -1970,16 +2200,18 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1970
2200
|
});
|
|
1971
2201
|
const task = core.createTask({ title: 'Cloud sourcePath reject test' }, { workspaceId: 'workspace-1' });
|
|
1972
2202
|
core.close();
|
|
2203
|
+
const sourcePath = path.join(externalRoot, 'outside.md');
|
|
2204
|
+
fs.writeFileSync(sourcePath, '# Outside approved roots', 'utf8');
|
|
1973
2205
|
const result = await callTool(projectRoot, 'cloud', 'save_task_attachment', {
|
|
1974
2206
|
id: task.id,
|
|
1975
|
-
filename: '
|
|
1976
|
-
sourcePath
|
|
2207
|
+
filename: 'outside.md',
|
|
2208
|
+
sourcePath
|
|
1977
2209
|
});
|
|
1978
|
-
expect(result.content?.[0]?.text).toContain('
|
|
1979
|
-
expect(result.content?.[0]?.text).toContain('resend the attachment using inline content instead');
|
|
2210
|
+
expect(result.content?.[0]?.text).toContain('sourcePath must be within project root, a Taskforce-managed attachment source, or an approved agent workspace root');
|
|
1980
2211
|
}
|
|
1981
2212
|
finally {
|
|
1982
2213
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
2214
|
+
fs.rmSync(externalRoot, { recursive: true, force: true });
|
|
1983
2215
|
}
|
|
1984
2216
|
});
|
|
1985
2217
|
it('accepts task references for save_task_attachment', async () => {
|
|
@@ -2331,7 +2563,7 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
2331
2563
|
filename: 'outside.md',
|
|
2332
2564
|
sourcePath: externalSourcePath
|
|
2333
2565
|
});
|
|
2334
|
-
expect(result.content?.[0]?.text).toContain('sourcePath must be within project root
|
|
2566
|
+
expect(result.content?.[0]?.text).toContain('sourcePath must be within project root, a Taskforce-managed attachment source, or an approved agent workspace root');
|
|
2335
2567
|
}
|
|
2336
2568
|
finally {
|
|
2337
2569
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { WorkspaceAssetEntry, WorkspaceAssetKind, WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
2
|
+
import type { McpTaskAttachmentDescriptor, TaskAttachmentRecord } from './taskAttachmentTypes.js';
|
|
3
|
+
export interface TaskAttachmentHelpersDeps {
|
|
4
|
+
activeWorkspaceId: string;
|
|
5
|
+
workspaceAssetStore: WorkspaceAssetStore | null;
|
|
6
|
+
getImageReferenceLabel: (asset: WorkspaceAssetEntry | null | undefined) => string | null;
|
|
7
|
+
getDocumentReferenceLabel: (asset: WorkspaceAssetEntry | null | undefined) => string | null;
|
|
8
|
+
inferMimeTypeFromPath: (filePath: string) => string;
|
|
9
|
+
inferAssetKind: (filePath: string, mimeType: string) => WorkspaceAssetKind;
|
|
10
|
+
isTextReadableMimeType: (mimeTypeRaw: string) => boolean;
|
|
11
|
+
isValidCanonicalDocumentAsset: (params: Pick<WorkspaceAssetEntry, 'kind' | 'storageKey' | 'originalFilename' | 'mimeType'>) => boolean;
|
|
12
|
+
canonicalAssetHelpers: {
|
|
13
|
+
getAttachmentAssetRecord: (attachment: TaskAttachmentRecord) => WorkspaceAssetEntry | null;
|
|
14
|
+
normalizeAttachmentStorageKey: (pathValue: unknown, fsPathValue: unknown) => string | null;
|
|
15
|
+
buildSyntheticAttachmentId: (taskId: string, attachment: TaskAttachmentRecord) => string;
|
|
16
|
+
toTaskAttachmentRecord: (attachment: string | TaskAttachmentRecord) => TaskAttachmentRecord;
|
|
17
|
+
};
|
|
18
|
+
documentHelpers: {
|
|
19
|
+
buildCanonicalDocumentAttachmentHint: (asset: {
|
|
20
|
+
assetId: string;
|
|
21
|
+
documentId: string | null;
|
|
22
|
+
referenceNumber?: number | null;
|
|
23
|
+
mimeType?: string | null;
|
|
24
|
+
}) => {
|
|
25
|
+
documentId: string | null;
|
|
26
|
+
preferredNextTool: string;
|
|
27
|
+
preferredWorkflow: string;
|
|
28
|
+
suggestedArgs: {
|
|
29
|
+
documentId: string;
|
|
30
|
+
};
|
|
31
|
+
guidance: string;
|
|
32
|
+
} | null;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare function createTaskAttachmentHelpers(deps: TaskAttachmentHelpersDeps): {
|
|
36
|
+
describeTaskAttachment: (taskId: string, attachmentInput: string | TaskAttachmentRecord) => McpTaskAttachmentDescriptor;
|
|
37
|
+
listTaskAttachmentDescriptors: (task: {
|
|
38
|
+
id: string;
|
|
39
|
+
attachments?: Array<string | TaskAttachmentRecord> | null;
|
|
40
|
+
}) => McpTaskAttachmentDescriptor[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function createTaskAttachmentHelpers(deps) {
|
|
2
|
+
const isCanonicalDocumentAssetRecord = (asset) => deps.isValidCanonicalDocumentAsset(asset);
|
|
3
|
+
const describeTaskAttachment = (taskId, attachmentInput) => {
|
|
4
|
+
const attachment = deps.canonicalAssetHelpers.toTaskAttachmentRecord(attachmentInput);
|
|
5
|
+
const asset = deps.canonicalAssetHelpers.getAttachmentAssetRecord(attachment);
|
|
6
|
+
const storageKey = deps.canonicalAssetHelpers.normalizeAttachmentStorageKey(attachment.path, attachment.fsPath);
|
|
7
|
+
const assetId = String(attachment.assetId || asset?.assetId || '').trim() || undefined;
|
|
8
|
+
const source = (asset?.storageProvider === 'external' || /^https?:\/\//i.test(String(attachment.path || '').trim()))
|
|
9
|
+
? 'external'
|
|
10
|
+
: 'canonical';
|
|
11
|
+
const mimeType = String(asset?.mimeType
|
|
12
|
+
|| (storageKey && !/^https?:\/\//i.test(storageKey) ? deps.inferMimeTypeFromPath(storageKey) : '')
|
|
13
|
+
|| deps.inferMimeTypeFromPath(String(attachment.originalFilename || attachment.displayName || attachment.caption || attachment.path || ''))).trim() || 'application/octet-stream';
|
|
14
|
+
const kind = asset?.kind || deps.inferAssetKind(storageKey || String(attachment.path || ''), mimeType);
|
|
15
|
+
const isCanonicalDocument = Boolean(asset && isCanonicalDocumentAssetRecord(asset)) || (!asset && kind === 'document');
|
|
16
|
+
const referenceNumber = typeof attachment.referenceNumber === 'number'
|
|
17
|
+
? attachment.referenceNumber
|
|
18
|
+
: (typeof asset?.referenceNumber === 'number' ? asset.referenceNumber : null);
|
|
19
|
+
const referenceLabel = String(attachment.referenceLabel
|
|
20
|
+
|| (kind === 'image' ? deps.getImageReferenceLabel(asset) : '')
|
|
21
|
+
|| (isCanonicalDocument ? deps.getDocumentReferenceLabel(asset) : '')).trim() || null;
|
|
22
|
+
const readableAsText = source === 'canonical' && deps.isTextReadableMimeType(mimeType);
|
|
23
|
+
const canonicalDocumentHint = isCanonicalDocument && source === 'canonical' && asset
|
|
24
|
+
? deps.documentHelpers.buildCanonicalDocumentAttachmentHint(asset)
|
|
25
|
+
: null;
|
|
26
|
+
return {
|
|
27
|
+
attachmentId: assetId || deps.canonicalAssetHelpers.buildSyntheticAttachmentId(taskId, attachment),
|
|
28
|
+
...(assetId ? { assetId } : {}),
|
|
29
|
+
...(referenceNumber ? { referenceNumber } : {}),
|
|
30
|
+
...(referenceLabel ? { referenceLabel } : {}),
|
|
31
|
+
...(canonicalDocumentHint?.documentId ? { documentId: canonicalDocumentHint.documentId } : {}),
|
|
32
|
+
path: String(attachment.path || '').trim(),
|
|
33
|
+
...(storageKey && !/^https?:\/\//i.test(storageKey) ? { fsPath: storageKey } : (attachment.fsPath ? { fsPath: String(attachment.fsPath).trim() } : {})),
|
|
34
|
+
...(attachment.caption ? { caption: String(attachment.caption).trim() } : {}),
|
|
35
|
+
...(attachment.displayName ? { displayName: String(attachment.displayName).trim() } : {}),
|
|
36
|
+
...(attachment.originalFilename ? { originalFilename: String(attachment.originalFilename).trim() } : {}),
|
|
37
|
+
kind: isCanonicalDocument ? 'document' : kind,
|
|
38
|
+
mimeType,
|
|
39
|
+
sizeBytes: asset ? Number(asset.sizeBytes || 0) : null,
|
|
40
|
+
...(attachment.timestamp ? { timestamp: String(attachment.timestamp).trim() } : {}),
|
|
41
|
+
source,
|
|
42
|
+
readableAsText,
|
|
43
|
+
...(canonicalDocumentHint ? {
|
|
44
|
+
preferredNextTool: canonicalDocumentHint.preferredNextTool,
|
|
45
|
+
preferredWorkflow: canonicalDocumentHint.preferredWorkflow,
|
|
46
|
+
suggestedArgs: canonicalDocumentHint.suggestedArgs,
|
|
47
|
+
guidance: canonicalDocumentHint.guidance,
|
|
48
|
+
} : {}),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const listTaskAttachmentDescriptors = (task) => {
|
|
52
|
+
if (!task || !Array.isArray(task.attachments))
|
|
53
|
+
return [];
|
|
54
|
+
return task.attachments.map((attachment) => describeTaskAttachment(task.id, attachment));
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
describeTaskAttachment,
|
|
58
|
+
listTaskAttachmentDescriptors,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { WorkspaceAssetKind } from '../storage/workspaceAssetStore.js';
|
|
2
|
+
export type TaskAttachmentRecord = {
|
|
3
|
+
path: string;
|
|
4
|
+
fsPath?: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
originalFilename?: string;
|
|
8
|
+
timestamp?: string;
|
|
9
|
+
assetId?: string;
|
|
10
|
+
referenceNumber?: number | null;
|
|
11
|
+
referenceLabel?: string | null;
|
|
12
|
+
taskId?: string | null;
|
|
13
|
+
};
|
|
14
|
+
export type McpTaskAttachmentDescriptor = {
|
|
15
|
+
attachmentId: string;
|
|
16
|
+
assetId?: string;
|
|
17
|
+
referenceNumber?: number | null;
|
|
18
|
+
referenceLabel?: string | null;
|
|
19
|
+
documentId?: string | null;
|
|
20
|
+
path: string;
|
|
21
|
+
fsPath?: string;
|
|
22
|
+
caption?: string;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
originalFilename?: string;
|
|
25
|
+
kind: WorkspaceAssetKind;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
sizeBytes: number | null;
|
|
28
|
+
timestamp?: string;
|
|
29
|
+
source: 'canonical' | 'external';
|
|
30
|
+
readableAsText: boolean;
|
|
31
|
+
preferredNextTool?: string;
|
|
32
|
+
preferredWorkflow?: string;
|
|
33
|
+
suggestedArgs?: {
|
|
34
|
+
documentId: string;
|
|
35
|
+
};
|
|
36
|
+
guidance?: string;
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|