feat: add SESSION_COMMAND_USER_IDS for admin session command access

Allow configured user IDs to execute session commands (/clear etc.)
in non-main groups. Adds isAdminSender check alongside isFromMe.
This commit is contained in:
Eyejoker
2026-03-16 05:14:38 +09:00
parent 7e77f37948
commit 6f38f1cd63
7 changed files with 170 additions and 68 deletions

View File

@@ -47,19 +47,23 @@ describe('extractSessionCommand', () => {
describe('isSessionCommandAllowed', () => {
it('allows main group regardless of sender', () => {
expect(isSessionCommandAllowed(true, false)).toBe(true);
expect(isSessionCommandAllowed(true, false, false)).toBe(true);
});
it('allows trusted/admin sender (is_from_me) in non-main group', () => {
expect(isSessionCommandAllowed(false, true)).toBe(true);
expect(isSessionCommandAllowed(false, true, false)).toBe(true);
});
it('allows configured admin sender in non-main group', () => {
expect(isSessionCommandAllowed(false, false, true)).toBe(true);
});
it('denies untrusted sender in non-main group', () => {
expect(isSessionCommandAllowed(false, false)).toBe(false);
expect(isSessionCommandAllowed(false, false, false)).toBe(false);
});
it('allows trusted sender in main group', () => {
expect(isSessionCommandAllowed(true, true)).toBe(true);
expect(isSessionCommandAllowed(true, true, false)).toBe(true);
});
});
@@ -89,6 +93,7 @@ function makeDeps(
clearSession: vi.fn(),
advanceCursor: vi.fn(),
formatMessages: vi.fn().mockReturnValue('<formatted>'),
isAdminSender: vi.fn().mockReturnValue(false),
canSenderInteract: vi.fn().mockReturnValue(true),
...overrides,
};
@@ -228,6 +233,25 @@ describe('handleSessionCommand', () => {
);
});
it('allows configured admin sender in non-main group', async () => {
const deps = makeDeps({
isAdminSender: vi.fn().mockReturnValue(true),
});
const result = await handleSessionCommand({
missedMessages: [makeMsg('/clear', { is_from_me: false, sender: 'discord-user-1' })],
isMainGroup: false,
groupName: 'test',
triggerPattern: trigger,
timezone: 'UTC',
deps,
});
expect(result).toEqual({ handled: true, success: true });
expect(deps.clearSession).toHaveBeenCalledTimes(1);
expect(deps.sendMessage).toHaveBeenCalledWith(
'Current session cleared. The next message will start a new conversation.',
);
});
it('reports failure when command-stage runAgent returns error without streamed status', async () => {
// runAgent resolves 'error' but callback never gets status: 'error'
const deps = makeDeps({