@hexabot-ai/api 3.2.7 → 3.3.0-beta.0

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.
Files changed (99) hide show
  1. package/dist/channel/channel.module.js +2 -0
  2. package/dist/channel/channel.module.js.map +1 -1
  3. package/dist/channel/entities/source.entity.d.ts +38 -0
  4. package/dist/channel/webhook.controller.d.ts +4 -1
  5. package/dist/channel/webhook.controller.js +19 -1
  6. package/dist/channel/webhook.controller.js.map +1 -1
  7. package/dist/chat/services/chat.service.js +4 -2
  8. package/dist/chat/services/chat.service.js.map +1 -1
  9. package/dist/chat/services/thread.service.js +6 -2
  10. package/dist/chat/services/thread.service.js.map +1 -1
  11. package/dist/mcp/tools/hexabot-mcp-tool.base.d.ts +0 -3
  12. package/dist/mcp/tools/hexabot-mcp-tool.base.js +0 -6
  13. package/dist/mcp/tools/hexabot-mcp-tool.base.js.map +1 -1
  14. package/dist/mcp/tools/hexabot-mcp.utils.d.ts +3 -0
  15. package/dist/mcp/tools/hexabot-mcp.utils.js +8 -1
  16. package/dist/mcp/tools/hexabot-mcp.utils.js.map +1 -1
  17. package/dist/mcp/tools/workflow-mcp.helper.d.ts +19 -1
  18. package/dist/mcp/tools/workflow-mcp.helper.js +2 -7
  19. package/dist/mcp/tools/workflow-mcp.helper.js.map +1 -1
  20. package/dist/mcp/tools/workflow-mcp.tools.d.ts +114 -0
  21. package/dist/mcp/tools/workflow-run-mcp.tools.d.ts +38 -0
  22. package/dist/mcp/tools/workflow-run-mcp.tools.js +1 -1
  23. package/dist/mcp/tools/workflow-run-mcp.tools.js.map +1 -1
  24. package/dist/mcp/tools/workflow-version-mcp.tools.d.ts +0 -22
  25. package/dist/mcp/tools/workflow-version-mcp.tools.js +3 -24
  26. package/dist/mcp/tools/workflow-version-mcp.tools.js.map +1 -1
  27. package/dist/static/assets/{cssMode-v8DOzA4o.js → cssMode-CVfTJuzy.js} +1 -1
  28. package/dist/static/assets/{freemarker2-B92dv90I.js → freemarker2-BA7GKqB7.js} +1 -1
  29. package/dist/static/assets/{handlebars-Dw3f_475.js → handlebars-BCwvKzIX.js} +1 -1
  30. package/dist/static/assets/{html-BLTdldWn.js → html-f6_JN1YY.js} +1 -1
  31. package/dist/static/assets/{htmlMode-tP1xHfwv.js → htmlMode-f4asMwRs.js} +1 -1
  32. package/dist/static/assets/index-CLS5CNoN.css +1 -0
  33. package/dist/static/assets/{index-1C1RUhvE.js → index-X6nbc-0C.js} +399 -392
  34. package/dist/static/assets/{javascript-D6zFkYDA.js → javascript-DaS6vtZ9.js} +1 -1
  35. package/dist/static/assets/{jsonMode-Casc9QAu.js → jsonMode-Ct41J50t.js} +1 -1
  36. package/dist/static/assets/{liquid-CiiGxWfs.js → liquid-4giXfRN2.js} +1 -1
  37. package/dist/static/assets/{lspLanguageFeatures-DDa9NCMy.js → lspLanguageFeatures-CqJlQWTJ.js} +1 -1
  38. package/dist/static/assets/{mdx-CkcXhfmV.js → mdx-DcO2npTr.js} +1 -1
  39. package/dist/static/assets/{python-CBPYdNzT.js → python-D7O1gNbd.js} +1 -1
  40. package/dist/static/assets/{razor-BU2J-T3U.js → razor-K5r5YQ4D.js} +1 -1
  41. package/dist/static/assets/{tsMode-CMXRM61t.js → tsMode-CvIEOKtw.js} +1 -1
  42. package/dist/static/assets/{typescript-GDvJ0GIf.js → typescript-BKjAKJn9.js} +1 -1
  43. package/dist/static/assets/{xml-CSlsq1iC.js → xml-BpIa2Zv1.js} +1 -1
  44. package/dist/static/assets/{yaml-stKXP2Du.js → yaml-BJzMbZhP.js} +1 -1
  45. package/dist/static/index.html +2 -2
  46. package/dist/static/locales/en/translation.json +75 -2
  47. package/dist/static/locales/fr/translation.json +72 -1
  48. package/dist/tsconfig.build.tsbuildinfo +1 -1
  49. package/dist/user/services/passwordReset.service.js +2 -1
  50. package/dist/user/services/passwordReset.service.js.map +1 -1
  51. package/dist/workflow/controllers/workflow-version.controller.d.ts +38 -0
  52. package/dist/workflow/controllers/workflow.controller.d.ts +43 -6
  53. package/dist/workflow/controllers/workflow.controller.js +23 -13
  54. package/dist/workflow/controllers/workflow.controller.js.map +1 -1
  55. package/dist/workflow/dto/workflow.dto.d.ts +40 -1
  56. package/dist/workflow/dto/workflow.dto.js +10 -0
  57. package/dist/workflow/dto/workflow.dto.js.map +1 -1
  58. package/dist/workflow/entities/memory-record.entity.d.ts +38 -0
  59. package/dist/workflow/entities/workflow-run.entity.d.ts +38 -0
  60. package/dist/workflow/entities/workflow-version.entity.d.ts +38 -0
  61. package/dist/workflow/entities/workflow.entity.d.ts +78 -0
  62. package/dist/workflow/entities/workflow.entity.js +4 -0
  63. package/dist/workflow/entities/workflow.entity.js.map +1 -1
  64. package/dist/workflow/guards/webhook-trigger.guard.d.ts +17 -0
  65. package/dist/workflow/guards/webhook-trigger.guard.js +139 -0
  66. package/dist/workflow/guards/webhook-trigger.guard.js.map +1 -0
  67. package/dist/workflow/services/agentic.service.d.ts +14 -2
  68. package/dist/workflow/services/agentic.service.js +17 -4
  69. package/dist/workflow/services/agentic.service.js.map +1 -1
  70. package/dist/workflow/services/webhook-trigger.service.d.ts +27 -0
  71. package/dist/workflow/services/webhook-trigger.service.js +90 -0
  72. package/dist/workflow/services/webhook-trigger.service.js.map +1 -0
  73. package/dist/workflow/services/workflow-run.service.d.ts +1 -0
  74. package/dist/workflow/services/workflow-run.service.js +9 -0
  75. package/dist/workflow/services/workflow-run.service.js.map +1 -1
  76. package/dist/workflow/workflow.module.js +8 -0
  77. package/dist/workflow/workflow.module.js.map +1 -1
  78. package/package.json +5 -6
  79. package/src/channel/README.md +17 -0
  80. package/src/channel/channel.module.ts +4 -0
  81. package/src/channel/webhook.controller.ts +46 -1
  82. package/src/chat/services/chat.service.ts +5 -4
  83. package/src/chat/services/thread.service.ts +10 -2
  84. package/src/mcp/README.md +2 -3
  85. package/src/mcp/tools/hexabot-mcp-tool.base.ts +0 -10
  86. package/src/mcp/tools/hexabot-mcp.utils.ts +10 -0
  87. package/src/mcp/tools/workflow-mcp.helper.ts +3 -11
  88. package/src/mcp/tools/workflow-run-mcp.tools.ts +2 -2
  89. package/src/mcp/tools/workflow-version-mcp.tools.ts +3 -29
  90. package/src/user/services/passwordReset.service.ts +9 -2
  91. package/src/workflow/controllers/workflow.controller.ts +36 -10
  92. package/src/workflow/dto/workflow.dto.ts +12 -0
  93. package/src/workflow/entities/workflow.entity.ts +11 -1
  94. package/src/workflow/guards/webhook-trigger.guard.ts +193 -0
  95. package/src/workflow/services/agentic.service.ts +27 -3
  96. package/src/workflow/services/webhook-trigger.service.ts +169 -0
  97. package/src/workflow/services/workflow-run.service.ts +21 -0
  98. package/src/workflow/workflow.module.ts +8 -0
  99. package/dist/static/assets/index-DyBPx3f5.css +0 -1
@@ -0,0 +1,193 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { timingSafeEqual } from 'node:crypto';
8
+
9
+ import { WebhookAuthType, WebhookTriggerConfig } from '@hexabot-ai/types';
10
+ import {
11
+ CanActivate,
12
+ ExecutionContext,
13
+ HttpStatus,
14
+ Injectable,
15
+ NotFoundException,
16
+ ParseUUIDPipe,
17
+ UnauthorizedException,
18
+ } from '@nestjs/common';
19
+ import { JwtService } from '@nestjs/jwt';
20
+ import { Request } from 'express';
21
+
22
+ import { LoggerService } from '@/logger/logger.service';
23
+ import { CredentialService } from '@/user/services/credential.service';
24
+
25
+ import { WorkflowService } from '../services/workflow.service';
26
+ import { WorkflowType } from '../types';
27
+
28
+ /**
29
+ * Guards the public webhook trigger endpoint. It resolves the target workflow,
30
+ * enforces that it is a manual workflow with an enabled webhook, and verifies
31
+ * the configured credentials. The handler re-resolves the workflow rather than
32
+ * receiving it through mutated request state.
33
+ */
34
+ @Injectable()
35
+ export class WebhookTriggerGuard implements CanActivate {
36
+ private readonly uuidPipe = new ParseUUIDPipe({
37
+ version: '4',
38
+ errorHttpStatusCode: HttpStatus.NOT_FOUND,
39
+ });
40
+
41
+ constructor(
42
+ private readonly workflowService: WorkflowService,
43
+ private readonly credentialService: CredentialService,
44
+ private readonly jwtService: JwtService,
45
+ private readonly logger: LoggerService,
46
+ ) {}
47
+
48
+ async canActivate(context: ExecutionContext): Promise<boolean> {
49
+ const request = context.switchToHttp().getRequest<Request>();
50
+ const id = await this.uuidPipe.transform(request.params.id, {
51
+ type: 'param',
52
+ data: 'id',
53
+ });
54
+ const workflow = await this.workflowService.findOne(id);
55
+ if (!workflow) {
56
+ throw new NotFoundException(`Workflow with ID ${id} not found`);
57
+ }
58
+
59
+ // Do not disclose whether the workflow exists when it is not exposed as a
60
+ // webhook (wrong type or webhook disabled).
61
+ if (workflow.type !== WorkflowType.manual) {
62
+ throw new NotFoundException(`Workflow with ID ${id} not found`);
63
+ }
64
+
65
+ const webhook = workflow.webhookTrigger;
66
+ if (!webhook?.enabled) {
67
+ throw new NotFoundException(`Workflow with ID ${id} not found`);
68
+ }
69
+
70
+ await this.verifyWebhookAuth(webhook, request);
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Resolves the secret referenced by the trigger config from the credentials
77
+ * store. A missing reference or a dangling credential must never
78
+ * authenticate: both fail closed with a generic 401, with a server-side
79
+ * warning so operators can diagnose broken references.
80
+ */
81
+ private async resolveSecret(
82
+ credentialId: string | null | undefined,
83
+ ): Promise<string> {
84
+ if (!credentialId) {
85
+ throw new UnauthorizedException('Invalid webhook credentials');
86
+ }
87
+
88
+ const value = await this.credentialService.findOneValue(credentialId);
89
+ if (!value) {
90
+ this.logger.warn(
91
+ `Webhook trigger references a missing credential (${credentialId})`,
92
+ );
93
+ throw new UnauthorizedException('Invalid webhook credentials');
94
+ }
95
+
96
+ return value;
97
+ }
98
+
99
+ /**
100
+ * Validates incoming webhook credentials against the workflow configuration.
101
+ * Throws {@link UnauthorizedException} when the credentials are missing or invalid.
102
+ */
103
+ private async verifyWebhookAuth(
104
+ webhook: WebhookTriggerConfig,
105
+ req: Request,
106
+ ): Promise<void> {
107
+ switch (webhook.authType) {
108
+ case WebhookAuthType.none:
109
+ return;
110
+
111
+ case WebhookAuthType.basic: {
112
+ // A missing expected secret must never authenticate empty credentials.
113
+ if (!webhook.username) {
114
+ throw new UnauthorizedException('Invalid webhook credentials');
115
+ }
116
+ const expectedPassword = await this.resolveSecret(
117
+ webhook.passwordCredentialId,
118
+ );
119
+ const header = req.headers.authorization ?? '';
120
+ const match = header.match(/^Basic\s+(\S+)$/i);
121
+ if (!match) {
122
+ throw new UnauthorizedException('Invalid webhook credentials');
123
+ }
124
+ const decoded = Buffer.from(match[1], 'base64').toString('utf8');
125
+ const separator = decoded.indexOf(':');
126
+ const username =
127
+ separator === -1 ? decoded : decoded.slice(0, separator);
128
+ const password = separator === -1 ? '' : decoded.slice(separator + 1);
129
+ if (
130
+ !this.safeEqual(username, webhook.username) ||
131
+ !this.safeEqual(password, expectedPassword)
132
+ ) {
133
+ throw new UnauthorizedException('Invalid webhook credentials');
134
+ }
135
+
136
+ return;
137
+ }
138
+
139
+ case WebhookAuthType.header: {
140
+ const headerName = (webhook.headerName ?? '').toLowerCase();
141
+ // A missing expected secret must never authenticate empty credentials.
142
+ if (!headerName) {
143
+ throw new UnauthorizedException('Invalid webhook credentials');
144
+ }
145
+ const expectedValue = await this.resolveSecret(
146
+ webhook.headerValueCredentialId,
147
+ );
148
+ const provided = req.headers[headerName];
149
+ const value = Array.isArray(provided) ? provided[0] : (provided ?? '');
150
+ if (!this.safeEqual(value, expectedValue)) {
151
+ throw new UnauthorizedException('Invalid webhook credentials');
152
+ }
153
+
154
+ return;
155
+ }
156
+
157
+ case WebhookAuthType.jwt: {
158
+ const secret = await this.resolveSecret(webhook.jwtSecretCredentialId);
159
+ const header = req.headers.authorization ?? '';
160
+ const match = header.match(/^Bearer\s+(\S+)$/i);
161
+ if (!match) {
162
+ throw new UnauthorizedException('Invalid webhook credentials');
163
+ }
164
+ try {
165
+ this.jwtService.verify(match[1], {
166
+ secret,
167
+ algorithms: [webhook.jwtAlgorithm ?? 'HS256'],
168
+ });
169
+ } catch {
170
+ throw new UnauthorizedException('Invalid webhook credentials');
171
+ }
172
+
173
+ return;
174
+ }
175
+
176
+ default:
177
+ throw new UnauthorizedException('Invalid webhook credentials');
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Constant-time string comparison to avoid leaking secrets through timing.
183
+ */
184
+ private safeEqual(a: string, b: string): boolean {
185
+ const bufferA = Buffer.from(a, 'utf8');
186
+ const bufferB = Buffer.from(b, 'utf8');
187
+ if (bufferA.length !== bufferB.length) {
188
+ return false;
189
+ }
190
+
191
+ return timingSafeEqual(bufferA, bufferB);
192
+ }
193
+ }
@@ -13,6 +13,7 @@ import {
13
13
  } from '@hexabot-ai/agentic';
14
14
  import { WorkflowFull, WorkflowRunFull } from '@hexabot-ai/types';
15
15
  import { Injectable } from '@nestjs/common';
16
+ import { OnEvent } from '@nestjs/event-emitter';
16
17
 
17
18
  import { ActionService } from '@/actions/actions.service';
18
19
  import { RuntimeBindingsService } from '@/bindings/runtime-bindings.service';
@@ -57,9 +58,14 @@ export class AgenticService implements WorkflowCallService {
57
58
  /**
58
59
  * Process an event by resuming a suspended workflow run if it exists,
59
60
  * otherwise start a new run using the latest configured workflow.
61
+ *
62
+ * Callers that already resolved the target workflow (e.g. the webhook
63
+ * trigger path) can pass it through `options.workflow` to avoid a redundant
64
+ * lookup.
60
65
  */
61
66
  async handleEvent(
62
67
  event: TriggerEventWrapper,
68
+ options: { workflow?: WorkflowFull } = {},
63
69
  ): Promise<WorkflowRunFull | null> {
64
70
  const initiator = event.getInitiator();
65
71
  const requestedWorkflowId = event.getWorkflowId();
@@ -98,9 +104,11 @@ export class AgenticService implements WorkflowCallService {
98
104
  return execution.run;
99
105
  }
100
106
 
101
- const workflowToRun = requestedWorkflowId
102
- ? await this.workflowService.findOneAndPopulate(requestedWorkflowId)
103
- : await this.workflowService.pickWorkflow();
107
+ const workflowToRun =
108
+ options.workflow ??
109
+ (requestedWorkflowId
110
+ ? await this.workflowService.findOneAndPopulate(requestedWorkflowId)
111
+ : await this.workflowService.pickWorkflow());
104
112
  if (!workflowToRun) {
105
113
  this.logger.warn('No workflow available to handle incoming event', {
106
114
  requestedWorkflowId: requestedWorkflowId ?? null,
@@ -761,4 +769,20 @@ export class AgenticService implements WorkflowCallService {
761
769
 
762
770
  return typeof error === 'string' ? error : JSON.stringify(error);
763
771
  }
772
+
773
+ /**
774
+ * Abort all active workflow runs linked to a thread when that thread is closed.
775
+ */
776
+ @OnEvent('hook:thread:postUpdate')
777
+ async handleThreadClosed({
778
+ entity,
779
+ databaseEntity,
780
+ }: {
781
+ entity: { id: string; status: string };
782
+ databaseEntity: { id: string; status: string };
783
+ }): Promise<void> {
784
+ if (entity.status === 'closed' && databaseEntity.status !== 'closed') {
785
+ await this.workflowRunService.abortActiveRunsForThread(entity.id);
786
+ }
787
+ }
764
788
  }
@@ -0,0 +1,169 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import {
8
+ WebhookAuthType,
9
+ WorkflowFull,
10
+ WorkflowRun,
11
+ WorkflowRunFull,
12
+ } from '@hexabot-ai/types';
13
+ import {
14
+ BadRequestException,
15
+ Injectable,
16
+ NotFoundException,
17
+ UnprocessableEntityException,
18
+ } from '@nestjs/common';
19
+ import { JwtService } from '@nestjs/jwt';
20
+
21
+ import { CredentialService } from '@/user/services/credential.service';
22
+ import { UserService } from '@/user/services/user.service';
23
+
24
+ import {
25
+ ManualEventWrapper,
26
+ TriggerEventWrapper,
27
+ } from '../lib/trigger-event-wrapper';
28
+ import { WorkflowType } from '../types';
29
+
30
+ import { AgenticService } from './agentic.service';
31
+ import { WorkflowService } from './workflow.service';
32
+
33
+ /**
34
+ * Outcome of a triggered workflow run, exposed to webhook callers. Execution
35
+ * is synchronous, so the final status and workflow output are available.
36
+ */
37
+ export type WorkflowTriggerResult = {
38
+ runId: string;
39
+ status: WorkflowRun['status'];
40
+ output: Record<string, unknown> | null;
41
+ error: string | null;
42
+ };
43
+
44
+ /**
45
+ * A server-issued webhook trigger token. Tokens carry no expiry: they stay
46
+ * valid until the workflow's signing secret credential is rotated.
47
+ */
48
+ export type WebhookTokenResult = {
49
+ token: string;
50
+ };
51
+
52
+ @Injectable()
53
+ export class WebhookTriggerService {
54
+ constructor(
55
+ private readonly workflowService: WorkflowService,
56
+ private readonly userService: UserService,
57
+ private readonly agenticService: AgenticService,
58
+ private readonly credentialService: CredentialService,
59
+ private readonly jwtService: JwtService,
60
+ ) {}
61
+
62
+ /**
63
+ * Signs a webhook trigger token with the workflow's own JWT secret so
64
+ * callers never have to craft tokens themselves. Tokens carry no expiry;
65
+ * rotating the secret credential invalidates every previously issued
66
+ * token. The workflow must be a manual workflow with an enabled,
67
+ * JWT-authenticated webhook trigger.
68
+ *
69
+ * @param id - The workflow ID the token is scoped to.
70
+ */
71
+ async generateToken(id: string): Promise<WebhookTokenResult> {
72
+ const workflow = await this.workflowService.findOne(id);
73
+ if (!workflow) {
74
+ throw new NotFoundException(`Workflow with ID ${id} not found`);
75
+ }
76
+
77
+ const webhook = workflow.webhookTrigger;
78
+ if (
79
+ workflow.type !== WorkflowType.manual ||
80
+ !webhook?.enabled ||
81
+ webhook.authType !== WebhookAuthType.jwt
82
+ ) {
83
+ throw new BadRequestException(
84
+ 'Workflow must have an enabled JWT-authenticated webhook trigger',
85
+ );
86
+ }
87
+
88
+ const secret = await this.credentialService.findOneValue(
89
+ webhook.jwtSecretCredentialId ?? undefined,
90
+ );
91
+ if (!secret) {
92
+ throw new BadRequestException(
93
+ 'The webhook trigger references a missing JWT secret credential',
94
+ );
95
+ }
96
+
97
+ const token = this.jwtService.sign(
98
+ { sub: id },
99
+ {
100
+ secret,
101
+ algorithm: webhook.jwtAlgorithm ?? 'HS256',
102
+ },
103
+ );
104
+
105
+ return { token };
106
+ }
107
+
108
+ /**
109
+ * Executes a manual workflow run from a webhook.
110
+ *
111
+ * Access control (existence, manual type, enabled webhook, credentials) is
112
+ * enforced upstream by {@link WebhookTriggerGuard}; this method resolves the
113
+ * workflow, validates the payload, and dispatches the event. The workflow
114
+ * runs to completion before responding, so the result carries the final run
115
+ * status and output.
116
+ *
117
+ * @param id - The workflow ID to execute.
118
+ * @param input - Optional workflow input payload.
119
+ */
120
+ async trigger(id: string, input: unknown): Promise<WorkflowTriggerResult> {
121
+ const workflow = await this.workflowService.findOneAndPopulate(id);
122
+ if (!workflow) {
123
+ throw new NotFoundException(`Workflow with ID ${id} not found`);
124
+ }
125
+
126
+ const manualInput = this.workflowService.validateManualInput(
127
+ input ?? {},
128
+ workflow.inputSchema,
129
+ );
130
+ const initiatorId = workflow.createdBy?.id ?? null;
131
+ const event = new ManualEventWrapper(manualInput, initiatorId);
132
+ const run = await this.dispatchTriggerEvent(workflow, event, initiatorId);
133
+ if (!run) {
134
+ // The run could not be created (missing initiator/definition) or the
135
+ // runner crashed before persisting a result.
136
+ throw new UnprocessableEntityException(
137
+ 'Workflow run could not be started',
138
+ );
139
+ }
140
+
141
+ return {
142
+ runId: run.id,
143
+ status: run.status,
144
+ output: run.output ?? null,
145
+ error: run.error ?? null,
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Resolves the initiator, targets the event at the given workflow, and hands
151
+ * it to the agentic runner. Shared by webhook triggers and authenticated
152
+ * manual runs so both paths dispatch identically.
153
+ */
154
+ async dispatchTriggerEvent(
155
+ workflow: WorkflowFull,
156
+ event: TriggerEventWrapper,
157
+ initiatorId: string | null,
158
+ ): Promise<WorkflowRunFull | null> {
159
+ const initiator = initiatorId
160
+ ? await this.userService.findOne(initiatorId)
161
+ : null;
162
+ if (initiator) {
163
+ event.setInitiator(initiator);
164
+ }
165
+ event.setWorkflowId(workflow.id);
166
+
167
+ return await this.agenticService.handleEvent(event, { workflow });
168
+ }
169
+ }
@@ -264,4 +264,25 @@ export class WorkflowRunService extends BaseOrmService<WorkflowRunOrmEntity> {
264
264
  );
265
265
  })[0];
266
266
  }
267
+
268
+ /**
269
+ * Mark all active runs linked to a thread as failed.
270
+ * Called when a thread is closed to abort any in-flight workflow execution.
271
+ *
272
+ * @param threadId - The ID of the closed thread.
273
+ */
274
+ async abortActiveRunsForThread(threadId: string): Promise<void> {
275
+ const activeRuns = await this.find({
276
+ where: {
277
+ thread: { id: threadId },
278
+ status: In(ACTIVE_WORKFLOW_RUN_STATUSES),
279
+ },
280
+ });
281
+
282
+ await Promise.all(
283
+ activeRuns.map((run) =>
284
+ this.markFailed(run.id, { error: 'Thread was closed' }),
285
+ ),
286
+ );
287
+ }
267
288
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { forwardRef, Module } from '@nestjs/common';
8
+ import { JwtModule } from '@nestjs/jwt';
8
9
  import { SchedulerRegistry } from '@nestjs/schedule';
9
10
  import { TypeOrmModule } from '@nestjs/typeorm';
10
11
 
@@ -28,6 +29,7 @@ import { MemoryRecordOrmEntity } from './entities/memory-record.entity';
28
29
  import { WorkflowRunOrmEntity } from './entities/workflow-run.entity';
29
30
  import { WorkflowVersionOrmEntity } from './entities/workflow-version.entity';
30
31
  import { WorkflowOrmEntity } from './entities/workflow.entity';
32
+ import { WebhookTriggerGuard } from './guards/webhook-trigger.guard';
31
33
  import { McpServerRepository } from './repositories/mcp-server.repository';
32
34
  import { MemoryDefinitionRepository } from './repositories/memory-definition.repository';
33
35
  import { MemoryRecordRepository } from './repositories/memory-record.repository';
@@ -42,6 +44,7 @@ import { McpServerService } from './services/mcp-server.service';
42
44
  import { MemoryDefinitionService } from './services/memory-definition.service';
43
45
  import { MemoryRecordService } from './services/memory-record.service';
44
46
  import { MemoryService } from './services/memory.service';
47
+ import { WebhookTriggerService } from './services/webhook-trigger.service';
45
48
  import { WorkflowRunService } from './services/workflow-run.service';
46
49
  import { WorkflowSchedulerService } from './services/workflow-scheduler.service';
47
50
  import { WorkflowVersionService } from './services/workflow-version.service';
@@ -62,6 +65,7 @@ import { WORKFLOW_CALL_SERVICE } from './types';
62
65
  forwardRef(() => CmsModule),
63
66
  forwardRef(() => ChatModule),
64
67
  UserModule,
68
+ JwtModule.register({}),
65
69
  ],
66
70
  controllers: [
67
71
  WorkflowController,
@@ -94,6 +98,8 @@ import { WORKFLOW_CALL_SERVICE } from './types';
94
98
  ScheduledWorkflowContext,
95
99
  WorkflowContextFactory,
96
100
  AgenticService,
101
+ WebhookTriggerService,
102
+ WebhookTriggerGuard,
97
103
  { provide: WORKFLOW_CALL_SERVICE, useExisting: AgenticService },
98
104
  ],
99
105
  exports: [
@@ -113,6 +119,8 @@ import { WORKFLOW_CALL_SERVICE } from './types';
113
119
  McpClientPoolService,
114
120
  ConversationalWorkflowContext,
115
121
  AgenticService,
122
+ WebhookTriggerService,
123
+ WebhookTriggerGuard,
116
124
  WORKFLOW_CALL_SERVICE,
117
125
  ],
118
126
  })