@@ -20,14 +20,15 @@ import {
2020 getManifest ,
2121 runAllChecks ,
2222 type CheckRunResult ,
23- type OAuthConfig ,
2423} from '@trycompai/integration-platform' ;
2524import { ConnectionRepository } from '../repositories/connection.repository' ;
2625import { ProviderRepository } from '../repositories/provider.repository' ;
2726import { CheckRunRepository } from '../repositories/check-run.repository' ;
2827import { CredentialVaultService } from '../services/credential-vault.service' ;
2928import { OAuthCredentialsService } from '../services/oauth-credentials.service' ;
29+ import { TaskIntegrationChecksService } from '../services/task-integration-checks.service' ;
3030import { getStringValue , toStringCredentials } from '../utils/credential-utils' ;
31+ import { isCheckDisabledForTask } from '../utils/disabled-task-checks' ;
3132import { db } from '@db' ;
3233import type { Prisma } from '@db' ;
3334
@@ -39,6 +40,8 @@ interface TaskIntegrationCheck {
3940 checkName : string ;
4041 checkDescription : string ;
4142 isConnected : boolean ;
43+ /** True when the check has been manually disconnected from this task. */
44+ isDisabledForTask : boolean ;
4245 needsConfiguration : boolean ;
4346 connectionId ?: string ;
4447 connectionStatus ?: string ;
@@ -56,6 +59,11 @@ interface RunCheckForTaskDto {
5659 checkId : string ;
5760}
5861
62+ interface ToggleCheckForTaskDto {
63+ connectionId : string ;
64+ checkId : string ;
65+ }
66+
5967@Controller ( { path : 'integrations/tasks' , version : '1' } )
6068@ApiTags ( 'Integrations' )
6169@UseGuards ( HybridAuthGuard , PermissionGuard )
@@ -69,18 +77,22 @@ export class TaskIntegrationsController {
6977 private readonly checkRunRepository : CheckRunRepository ,
7078 private readonly credentialVaultService : CredentialVaultService ,
7179 private readonly oauthCredentialsService : OAuthCredentialsService ,
80+ private readonly taskIntegrationChecksService : TaskIntegrationChecksService ,
7281 ) { }
7382
7483 /**
75- * Get all integration checks that can auto-complete a specific task template
84+ * Get all integration checks that can auto-complete a specific task template.
85+ * When a specific `taskId` is also provided, per-task disable state is
86+ * resolved from the matching connection's metadata so the UI can show
87+ * which checks have been manually disconnected from that task.
7688 */
7789 @Get ( 'template/:templateId/checks' )
7890 @RequirePermission ( 'integration' , 'read' )
7991 async getChecksForTaskTemplate (
8092 @Param ( 'templateId' ) templateId : string ,
8193 @OrganizationId ( ) organizationId : string ,
94+ taskIdForDisableState ?: string ,
8295 ) : Promise < { checks : TaskIntegrationCheck [ ] } > {
83-
8496 const manifests = getActiveManifests ( ) ;
8597 const checks : TaskIntegrationCheck [ ] = [ ] ;
8698
@@ -136,6 +148,15 @@ export class TaskIntegrationsController {
136148 oauthConfigured = availability . available ;
137149 }
138150
151+ const isDisabledForTask =
152+ ! ! taskIdForDisableState &&
153+ ! ! connection &&
154+ isCheckDisabledForTask (
155+ connection . metadata ,
156+ taskIdForDisableState ,
157+ check . id ,
158+ ) ;
159+
139160 checks . push ( {
140161 integrationId : manifest . id ,
141162 integrationName : manifest . name ,
@@ -144,6 +165,7 @@ export class TaskIntegrationsController {
144165 checkName : check . name ,
145166 checkDescription : check . description ,
146167 isConnected : ! ! connection && connection . status === 'active' ,
168+ isDisabledForTask,
147169 needsConfiguration,
148170 connectionId : connection ?. id ,
149171 connectionStatus : connection ?. status ,
@@ -169,7 +191,6 @@ export class TaskIntegrationsController {
169191 checks : TaskIntegrationCheck [ ] ;
170192 task : { id : string ; title : string ; templateId : string | null } ;
171193 } > {
172-
173194 // Get the task to find its template ID
174195 const task = await db . task . findUnique ( {
175196 where : { id : taskId , organizationId } ,
@@ -187,10 +208,11 @@ export class TaskIntegrationsController {
187208 } ;
188209 }
189210
190- // Get checks for this template
211+ // Get checks for this template, annotated with per-task disable state
191212 const { checks } = await this . getChecksForTaskTemplate (
192213 task . taskTemplateId ,
193214 organizationId ,
215+ task . id ,
194216 ) ;
195217
196218 return {
@@ -215,7 +237,6 @@ export class TaskIntegrationsController {
215237 checkRunId ?: string ;
216238 taskStatus ?: string | null ;
217239 } > {
218-
219240 const { connectionId, checkId } = body ;
220241
221242 // Verify task exists
@@ -240,6 +261,14 @@ export class TaskIntegrationsController {
240261 ) ;
241262 }
242263
264+ // Reject runs for checks that have been disconnected from this task.
265+ if ( isCheckDisabledForTask ( connection . metadata , taskId , checkId ) ) {
266+ throw new HttpException (
267+ 'This check is disconnected from the task. Reconnect it before running.' ,
268+ HttpStatus . BAD_REQUEST ,
269+ ) ;
270+ }
271+
243272 // Get provider and manifest
244273 const provider = await this . providerRepository . findById (
245274 connection . providerId ,
@@ -493,6 +522,48 @@ export class TaskIntegrationsController {
493522 }
494523 }
495524
525+ /**
526+ * Disconnect a single integration check from a specific task.
527+ * Does not affect the connection itself or any other task that uses the
528+ * same check. Scheduled runs, manual runs, and the task detail UI will all
529+ * skip this (task, check) pair until it is reconnected.
530+ */
531+ @Post ( ':taskId/checks/disconnect' )
532+ @RequirePermission ( 'integration' , 'update' )
533+ async disconnectCheckFromTask (
534+ @Param ( 'taskId' ) taskId : string ,
535+ @OrganizationId ( ) organizationId : string ,
536+ @Body ( ) body : ToggleCheckForTaskDto ,
537+ ) : Promise < { success : true ; disabled : true } > {
538+ await this . taskIntegrationChecksService . disconnectCheckFromTask ( {
539+ taskId,
540+ connectionId : body . connectionId ,
541+ checkId : body . checkId ,
542+ organizationId,
543+ } ) ;
544+ return { success : true , disabled : true } ;
545+ }
546+
547+ /**
548+ * Re-enable a previously disconnected integration check for a specific
549+ * task. Inverse of the disconnect endpoint.
550+ */
551+ @Post ( ':taskId/checks/reconnect' )
552+ @RequirePermission ( 'integration' , 'update' )
553+ async reconnectCheckToTask (
554+ @Param ( 'taskId' ) taskId : string ,
555+ @OrganizationId ( ) organizationId : string ,
556+ @Body ( ) body : ToggleCheckForTaskDto ,
557+ ) : Promise < { success : true ; disabled : false } > {
558+ await this . taskIntegrationChecksService . reconnectCheckToTask ( {
559+ taskId,
560+ connectionId : body . connectionId ,
561+ checkId : body . checkId ,
562+ organizationId,
563+ } ) ;
564+ return { success : true , disabled : false } ;
565+ }
566+
496567 /**
497568 * Get check run history for a task
498569 */
@@ -502,7 +573,6 @@ export class TaskIntegrationsController {
502573 @Param ( 'taskId' ) taskId : string ,
503574 @Query ( 'limit' ) limit ?: string ,
504575 ) {
505-
506576 const runs = await this . checkRunRepository . findByTask (
507577 taskId ,
508578 limit ? parseInt ( limit , 10 ) : 10 ,
0 commit comments