fix(#709): use durable Discord termination approvals
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
Jarvis
2026-07-13 01:02:37 -05:00
parent 689d5b6870
commit 34b82bd257

View File

@@ -1,4 +1,4 @@
import { Inject, Logger } from '@nestjs/common'; import { Inject, Logger, Optional } from '@nestjs/common';
import { import {
WebSocketGateway, WebSocketGateway,
WebSocketServer, WebSocketServer,
@@ -39,6 +39,7 @@ import {
import { BRAIN } from '../brain/brain.tokens.js'; import { BRAIN } from '../brain/brain.tokens.js';
import { CommandRegistryService } from '../commands/command-registry.service.js'; import { CommandRegistryService } from '../commands/command-registry.service.js';
import { CommandExecutorService } from '../commands/command-executor.service.js'; import { CommandExecutorService } from '../commands/command-executor.service.js';
import { CommandAuthorizationService } from '../commands/command-authorization.service.js';
import { RoutingEngineService } from '../agent/routing/routing-engine.service.js'; import { RoutingEngineService } from '../agent/routing/routing-engine.service.js';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ChatSocketMessageDto } from './chat.dto.js'; import { ChatSocketMessageDto } from './chat.dto.js';
@@ -124,6 +125,9 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
@Inject(CommandRegistryService) private readonly commandRegistry: CommandRegistryService, @Inject(CommandRegistryService) private readonly commandRegistry: CommandRegistryService,
@Inject(CommandExecutorService) private readonly commandExecutor: CommandExecutorService, @Inject(CommandExecutorService) private readonly commandExecutor: CommandExecutorService,
@Inject(RoutingEngineService) private readonly routingEngine: RoutingEngineService, @Inject(RoutingEngineService) private readonly routingEngine: RoutingEngineService,
@Optional()
@Inject(CommandAuthorizationService)
private readonly commandAuthorization: CommandAuthorizationService | null = null,
) {} ) {}
afterInit(): void { afterInit(): void {
@@ -646,19 +650,28 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
): Promise<void> { ): Promise<void> {
if (!client.data.discordService) return; if (!client.data.discordService) return;
const ingress = this.resolveDiscordIngress(client, envelope, 'approve'); const ingress = this.resolveDiscordIngress(client, envelope, 'approve');
const command = ingress?.content.match(/^\/approve\s+([a-z][\w-]*)$/i)?.[1]; const actionParts = ingress?.content.match(/^\/approve\s+([^\s]+)\s+([^\s]+)$/i);
const serviceUserId = process.env['DISCORD_SERVICE_USER_ID']; const serviceUserId = process.env['DISCORD_SERVICE_USER_ID'];
if (!ingress || !command || !serviceUserId) return; if (!ingress || !actionParts || !serviceUserId || !this.commandAuthorization) return;
const approval = await this.commandExecutor.createApproval( const binding = resolveDiscordInteractionBinding(
{ command, conversationId: ingress.conversationId }, parseDiscordInteractionBindings(process.env['DISCORD_INTERACTION_BINDINGS']),
{ ingress.guildId,
userId: serviceUserId, ingress.channelId,
tenantId: process.env['DISCORD_SERVICE_TENANT_ID'] ?? serviceUserId, ingress.userId,
}, 'approve',
); );
if (!binding) return;
const approval = await this.commandAuthorization.createRuntimeTerminationApproval({
providerId: actionParts[1]!,
sessionId: actionParts[2]!,
actorId: serviceUserId,
tenantId: process.env['DISCORD_SERVICE_TENANT_ID'] ?? serviceUserId,
channelId: ingress.channelId,
correlationId: ingress.correlationId,
agentName: binding.instanceId,
});
client.emit('discord:approval', { client.emit('discord:approval', {
correlationId: ingress.correlationId, correlationId: ingress.correlationId,
command,
success: approval !== null, success: approval !== null,
approvalId: approval?.approvalId, approvalId: approval?.approvalId,
expiresAt: approval?.expiresAt, expiresAt: approval?.expiresAt,