feat: add session-scoped workflow approval gate commands
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
import { createContextCommand, createElevateCommand, createModelCommand, createQueueCommand, createResearchCommand, createTransferCommand } from './index.js';
|
||||
import { createApproveCommand, createApprovalsCommand, createContextCommand, createDenyCommand, createElevateCommand, createModelCommand, createQueueCommand, createResearchCommand, createTransferCommand } from './index.js';
|
||||
|
||||
describe('builtin /model command', () => {
|
||||
it('passes through the full argument string', async () => {
|
||||
@@ -197,3 +197,47 @@ describe('builtin /transfer command', () => {
|
||||
expect(result).toEqual({ handled: true, text: 'Transfer command is not available in this session.' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('builtin approval commands', () => {
|
||||
it('calls getApprovals for /approvals', async () => {
|
||||
const cmd = createApprovalsCommand();
|
||||
const getApprovals = vi.fn(() => '1 pending');
|
||||
const result = await cmd.execute([], {
|
||||
channel: 'test',
|
||||
senderId: 'user',
|
||||
sessionId: 's1',
|
||||
rawInput: '/approvals',
|
||||
services: { getApprovals },
|
||||
});
|
||||
expect(getApprovals).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ handled: true, text: '1 pending' });
|
||||
});
|
||||
|
||||
it('passes raw input to approvePending for /approve', async () => {
|
||||
const cmd = createApproveCommand();
|
||||
const approvePending = vi.fn(() => 'approved');
|
||||
const result = await cmd.execute(['abc-123'], {
|
||||
channel: 'test',
|
||||
senderId: 'user',
|
||||
sessionId: 's1',
|
||||
rawInput: '/approve abc-123',
|
||||
services: { approvePending },
|
||||
});
|
||||
expect(approvePending).toHaveBeenCalledWith('abc-123');
|
||||
expect(result).toEqual({ handled: true, text: 'approved' });
|
||||
});
|
||||
|
||||
it('passes raw input to denyPending for /deny', async () => {
|
||||
const cmd = createDenyCommand();
|
||||
const denyPending = vi.fn(() => 'denied');
|
||||
const result = await cmd.execute(['abc-123', 'too', 'risky'], {
|
||||
channel: 'test',
|
||||
senderId: 'user',
|
||||
sessionId: 's1',
|
||||
rawInput: '/deny abc-123 too risky',
|
||||
services: { denyPending },
|
||||
});
|
||||
expect(denyPending).toHaveBeenCalledWith('abc-123 too risky');
|
||||
expect(result).toEqual({ handled: true, text: 'denied' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,6 +236,54 @@ export function createTransferCommand(): CommandDefinition {
|
||||
};
|
||||
}
|
||||
|
||||
export function createApprovalsCommand(): CommandDefinition {
|
||||
return {
|
||||
name: 'approvals',
|
||||
description: 'Show pending approval gates for this session',
|
||||
execute: async (_args, ctx) => {
|
||||
if (!ctx.services?.getApprovals) {
|
||||
return notAvailable('Approvals command');
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
text: await ctx.services.getApprovals(),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createApproveCommand(): CommandDefinition {
|
||||
return {
|
||||
name: 'approve',
|
||||
description: 'Approve a pending gate (latest by default, or by id)',
|
||||
execute: async (args, ctx) => {
|
||||
if (!ctx.services?.approvePending) {
|
||||
return notAvailable('Approve command');
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
text: await ctx.services.approvePending(args.join(' ').trim()),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createDenyCommand(): CommandDefinition {
|
||||
return {
|
||||
name: 'deny',
|
||||
description: 'Deny a pending gate (latest by default, optional id and reason)',
|
||||
execute: async (args, ctx) => {
|
||||
if (!ctx.services?.denyPending) {
|
||||
return notAvailable('Deny command');
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
text: await ctx.services.denyPending(args.join(' ').trim()),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function registerBuiltinCommands(registry: CommandRegistry): void {
|
||||
registry.register(createHelpCommand(registry));
|
||||
registry.register(createStatusCommand());
|
||||
@@ -248,4 +296,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
||||
registry.register(createElevateCommand());
|
||||
registry.register(createQueueCommand());
|
||||
registry.register(createTransferCommand());
|
||||
registry.register(createApprovalsCommand());
|
||||
registry.register(createApproveCommand());
|
||||
registry.register(createDenyCommand());
|
||||
}
|
||||
|
||||
@@ -10,5 +10,8 @@ export {
|
||||
createResetCommand,
|
||||
createQueueCommand,
|
||||
createTransferCommand,
|
||||
createApprovalsCommand,
|
||||
createApproveCommand,
|
||||
createDenyCommand,
|
||||
registerBuiltinCommands,
|
||||
} from './builtin/index.js';
|
||||
|
||||
@@ -35,4 +35,7 @@ export interface CommandServices {
|
||||
setQueue?: (input: string) => Promise<string> | string;
|
||||
resetQueue?: () => Promise<string> | string;
|
||||
transferSession?: (target: string) => Promise<string> | string;
|
||||
getApprovals?: () => Promise<string> | string;
|
||||
approvePending?: (input: string) => Promise<string> | string;
|
||||
denyPending?: (input: string) => Promise<string> | string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user