discord-agent-bridge 0.1.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.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/app.js +210 -0
- package/dist/app.js.map +1 -0
- package/dist/app.test.js +117 -0
- package/dist/app.test.js.map +1 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.js +91 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/core/auditLog.js +65 -0
- package/dist/core/auditLog.js.map +1 -0
- package/dist/core/auditLog.test.js +124 -0
- package/dist/core/auditLog.test.js.map +1 -0
- package/dist/core/auth.js +106 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/auth.test.js +216 -0
- package/dist/core/auth.test.js.map +1 -0
- package/dist/core/channelRegistry.js +119 -0
- package/dist/core/channelRegistry.js.map +1 -0
- package/dist/core/channelRegistry.test.js +114 -0
- package/dist/core/channelRegistry.test.js.map +1 -0
- package/dist/core/commandPolicy.js +6 -0
- package/dist/core/commandPolicy.js.map +1 -0
- package/dist/core/commandRouter.js +9 -0
- package/dist/core/commandRouter.js.map +1 -0
- package/dist/core/config.js +141 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.js +149 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/configResolver.js +90 -0
- package/dist/core/configResolver.js.map +1 -0
- package/dist/core/configResolver.test.js +138 -0
- package/dist/core/configResolver.test.js.map +1 -0
- package/dist/core/configSchema.js +164 -0
- package/dist/core/configSchema.js.map +1 -0
- package/dist/core/contracts.js +5 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/eventBus.js +42 -0
- package/dist/core/eventBus.js.map +1 -0
- package/dist/core/eventBus.test.js +72 -0
- package/dist/core/eventBus.test.js.map +1 -0
- package/dist/core/hookBridge.js +11 -0
- package/dist/core/hookBridge.js.map +1 -0
- package/dist/core/logger.js +116 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/logger.test.js +88 -0
- package/dist/core/logger.test.js.map +1 -0
- package/dist/core/modeRegistry.js +33 -0
- package/dist/core/modeRegistry.js.map +1 -0
- package/dist/core/permissionResolver.js +43 -0
- package/dist/core/permissionResolver.js.map +1 -0
- package/dist/core/permissionResolver.test.js +136 -0
- package/dist/core/permissionResolver.test.js.map +1 -0
- package/dist/core/providerCatalog.js +240 -0
- package/dist/core/providerCatalog.js.map +1 -0
- package/dist/core/providerCatalog.test.js +230 -0
- package/dist/core/providerCatalog.test.js.map +1 -0
- package/dist/core/sessionOrchestrator.js +463 -0
- package/dist/core/sessionOrchestrator.js.map +1 -0
- package/dist/core/sessionOrchestrator.test.js +344 -0
- package/dist/core/sessionOrchestrator.test.js.map +1 -0
- package/dist/core/state/schema.js +49 -0
- package/dist/core/state/schema.js.map +1 -0
- package/dist/core/state/store.js +86 -0
- package/dist/core/state/store.js.map +1 -0
- package/dist/core/state/store.test.js +99 -0
- package/dist/core/state/store.test.js.map +1 -0
- package/dist/core/usageService.js +310 -0
- package/dist/core/usageService.js.map +1 -0
- package/dist/core/usageService.test.js +234 -0
- package/dist/core/usageService.test.js.map +1 -0
- package/dist/discord/alwaysAllow.test.js +181 -0
- package/dist/discord/alwaysAllow.test.js.map +1 -0
- package/dist/discord/client.js +666 -0
- package/dist/discord/client.js.map +1 -0
- package/dist/discord/client.test.js +256 -0
- package/dist/discord/client.test.js.map +1 -0
- package/dist/discord/configPanel.js +270 -0
- package/dist/discord/configPanel.js.map +1 -0
- package/dist/discord/configPanel.test.js +332 -0
- package/dist/discord/configPanel.test.js.map +1 -0
- package/dist/discord/directoryBrowser.js +151 -0
- package/dist/discord/directoryBrowser.js.map +1 -0
- package/dist/discord/directoryBrowser.test.js +163 -0
- package/dist/discord/directoryBrowser.test.js.map +1 -0
- package/dist/discord/favorites.js +10 -0
- package/dist/discord/favorites.js.map +1 -0
- package/dist/discord/fileDownload.js +94 -0
- package/dist/discord/fileDownload.js.map +1 -0
- package/dist/discord/fileDownload.test.js +59 -0
- package/dist/discord/fileDownload.test.js.map +1 -0
- package/dist/discord/format.js +104 -0
- package/dist/discord/format.js.map +1 -0
- package/dist/discord/format.test.js +40 -0
- package/dist/discord/format.test.js.map +1 -0
- package/dist/discord/guildChannels.js +97 -0
- package/dist/discord/guildChannels.js.map +1 -0
- package/dist/discord/guildChannels.test.js +231 -0
- package/dist/discord/guildChannels.test.js.map +1 -0
- package/dist/discord/i18n.js +207 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/i18n.test.js +31 -0
- package/dist/discord/i18n.test.js.map +1 -0
- package/dist/discord/interactionRouter.js +954 -0
- package/dist/discord/interactionRouter.js.map +1 -0
- package/dist/discord/interactionRouter.test.js +1245 -0
- package/dist/discord/interactionRouter.test.js.map +1 -0
- package/dist/discord/messageRouter.js +221 -0
- package/dist/discord/messageRouter.js.map +1 -0
- package/dist/discord/messageRouter.test.js +360 -0
- package/dist/discord/messageRouter.test.js.map +1 -0
- package/dist/discord/ports.js +12 -0
- package/dist/discord/ports.js.map +1 -0
- package/dist/discord/renderers/defaultSet.test.js +97 -0
- package/dist/discord/renderers/defaultSet.test.js.map +1 -0
- package/dist/discord/renderers/diffView.js +73 -0
- package/dist/discord/renderers/diffView.js.map +1 -0
- package/dist/discord/renderers/diffView.test.js +54 -0
- package/dist/discord/renderers/diffView.test.js.map +1 -0
- package/dist/discord/renderers/index.js +143 -0
- package/dist/discord/renderers/index.js.map +1 -0
- package/dist/discord/renderers/index.test.js +124 -0
- package/dist/discord/renderers/index.test.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.js +19 -0
- package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
- package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.js +150 -0
- package/dist/discord/renderers/permissionButtons.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.test.js +108 -0
- package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
- package/dist/discord/renderers/resultLine.js +25 -0
- package/dist/discord/renderers/resultLine.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.js +20 -0
- package/dist/discord/renderers/statusEmbed.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.test.js +32 -0
- package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.js +134 -0
- package/dist/discord/renderers/streamEmbed.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.test.js +97 -0
- package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
- package/dist/discord/renderers/toolThread.js +65 -0
- package/dist/discord/renderers/toolThread.js.map +1 -0
- package/dist/discord/renderers/toolThread.test.js +54 -0
- package/dist/discord/renderers/toolThread.test.js.map +1 -0
- package/dist/discord/renderers/transcriptFeed.js +33 -0
- package/dist/discord/renderers/transcriptFeed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.js +78 -0
- package/dist/discord/renderers/usageEmbed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.test.js +42 -0
- package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
- package/dist/discord/wiring.js +214 -0
- package/dist/discord/wiring.js.map +1 -0
- package/dist/discord/wiring.test.js +252 -0
- package/dist/discord/wiring.test.js.map +1 -0
- package/dist/discord/wizard/channelWizard.js +305 -0
- package/dist/discord/wizard/channelWizard.js.map +1 -0
- package/dist/discord/wizard/channelWizard.test.js +298 -0
- package/dist/discord/wizard/channelWizard.test.js.map +1 -0
- package/dist/discord/wizard/resumeWizard.js +123 -0
- package/dist/discord/wizard/resumeWizard.js.map +1 -0
- package/dist/modes/claude/index.js +80 -0
- package/dist/modes/claude/index.js.map +1 -0
- package/dist/modes/claude/listResumable.test.js +72 -0
- package/dist/modes/claude/listResumable.test.js.map +1 -0
- package/dist/modes/claude/mcpFileTool.js +82 -0
- package/dist/modes/claude/mcpFileTool.js.map +1 -0
- package/dist/modes/claude/permissions.js +35 -0
- package/dist/modes/claude/permissions.js.map +1 -0
- package/dist/modes/claude/plugins.js +56 -0
- package/dist/modes/claude/plugins.js.map +1 -0
- package/dist/modes/claude/session.js +324 -0
- package/dist/modes/claude/session.js.map +1 -0
- package/dist/modes/claude/session.test.js +415 -0
- package/dist/modes/claude/session.test.js.map +1 -0
- package/dist/modes/codex/discovery.js +221 -0
- package/dist/modes/codex/discovery.js.map +1 -0
- package/dist/modes/codex/discovery.test.js +213 -0
- package/dist/modes/codex/discovery.test.js.map +1 -0
- package/dist/modes/codex/eventMapper.js +262 -0
- package/dist/modes/codex/eventMapper.js.map +1 -0
- package/dist/modes/codex/index.js +132 -0
- package/dist/modes/codex/index.js.map +1 -0
- package/dist/modes/codex/index.test.js +170 -0
- package/dist/modes/codex/index.test.js.map +1 -0
- package/dist/modes/codex/runner.js +207 -0
- package/dist/modes/codex/runner.js.map +1 -0
- package/dist/modes/codex/runner.test.js +587 -0
- package/dist/modes/codex/runner.test.js.map +1 -0
- package/dist/modes/codex/sqliteReader.js +171 -0
- package/dist/modes/codex/sqliteReader.js.map +1 -0
- package/dist/modes/codex/sqliteReader.test.js +137 -0
- package/dist/modes/codex/sqliteReader.test.js.map +1 -0
- package/dist/setup/wizard.js +115 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/setup/wizard.test.js +216 -0
- package/dist/setup/wizard.test.js.map +1 -0
- package/dist/startBot.test.js +78 -0
- package/dist/startBot.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { MessageRouter } from './messageRouter.js';
|
|
6
|
+
import { Authorizer } from '../core/auth.js';
|
|
7
|
+
import { ChannelRegistry } from '../core/channelRegistry.js';
|
|
8
|
+
import { EventBus } from '../core/eventBus.js';
|
|
9
|
+
import { ConfigStore } from '../core/config.js';
|
|
10
|
+
import { StateStore } from '../core/state/store.js';
|
|
11
|
+
import { CONFIG_VERSION } from '../core/configSchema.js';
|
|
12
|
+
import { createLogger } from '../core/logger.js';
|
|
13
|
+
// The discord.js Administrator bit (PermissionFlagsBits.Administrator === 1<<3). The
|
|
14
|
+
// router defaults to this value, so a test member's permissions.has must recognize it.
|
|
15
|
+
const ADMIN_BIT = 1n << 3n;
|
|
16
|
+
// A silent logger (no console noise in the test run).
|
|
17
|
+
const logger = createLogger('test', { level: 'error', sink: { write() { } } });
|
|
18
|
+
// A binding for a Claude channel rooted at `cwd`.
|
|
19
|
+
function binding(cwd, overrides = {}) {
|
|
20
|
+
return {
|
|
21
|
+
guildId: 'g1',
|
|
22
|
+
channelId: 'c1',
|
|
23
|
+
mode: 'claude',
|
|
24
|
+
sessionId: 's1',
|
|
25
|
+
cwd,
|
|
26
|
+
ownerId: 'owner',
|
|
27
|
+
permMode: 'default',
|
|
28
|
+
profile: null,
|
|
29
|
+
archived: false,
|
|
30
|
+
createdAt: 'now',
|
|
31
|
+
updatedAt: 'now',
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// A fake IncomingMessage with recording reply/react/removeReaction. `reactFails`
|
|
36
|
+
// makes react throw (a missing Add-Reactions permission) so a test can assert the
|
|
37
|
+
// failure never propagates out of the router.
|
|
38
|
+
function fakeMessage(over = {}) {
|
|
39
|
+
const replies = [];
|
|
40
|
+
const reactions = [];
|
|
41
|
+
const removed = [];
|
|
42
|
+
const attachments = over.attachments ?? [];
|
|
43
|
+
const message = {
|
|
44
|
+
content: over.content ?? 'hello agent',
|
|
45
|
+
guildId: over.guildId ?? 'g1',
|
|
46
|
+
channelId: over.channelId ?? 'c1',
|
|
47
|
+
author: over.author ?? { id: 'u1', bot: false },
|
|
48
|
+
member: over.member ?? { roles: { cache: { map: (fn) => ['role-exec'].map((id) => fn({ id })) } } },
|
|
49
|
+
attachments: { values: () => attachments },
|
|
50
|
+
react: async (emoji) => {
|
|
51
|
+
if (over.reactFails)
|
|
52
|
+
throw new Error('Missing Add Reactions permission');
|
|
53
|
+
reactions.push(emoji);
|
|
54
|
+
},
|
|
55
|
+
reply: async (content) => {
|
|
56
|
+
replies.push(content);
|
|
57
|
+
},
|
|
58
|
+
// Present by default so completion-clearing tests can assert ⏳ was removed;
|
|
59
|
+
// omit it (withRemove:false) to exercise the router's "no removeReaction" path.
|
|
60
|
+
...(over.withRemove === false
|
|
61
|
+
? {}
|
|
62
|
+
: {
|
|
63
|
+
removeReaction: async (emoji) => {
|
|
64
|
+
removed.push(emoji);
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
return { message, replies, reactions, removed };
|
|
69
|
+
}
|
|
70
|
+
// Registry/authorizer/orchestrator test doubles.
|
|
71
|
+
function makeDeps(opts) {
|
|
72
|
+
const sent = [];
|
|
73
|
+
const channelRegistry = {
|
|
74
|
+
get: (_g, _c) => opts.binding,
|
|
75
|
+
};
|
|
76
|
+
const authorizer = {
|
|
77
|
+
authorize: () => opts.auth ?? { allowed: true, tier: 'execute' },
|
|
78
|
+
};
|
|
79
|
+
const orchestrator = {
|
|
80
|
+
send: vi.fn(async (guildId, channelId, turn) => {
|
|
81
|
+
if (opts.sendThrows)
|
|
82
|
+
throw opts.sendThrows;
|
|
83
|
+
sent.push({ guildId, channelId, turn });
|
|
84
|
+
return opts.sendResult ?? { status: 'started', queueDepth: 1 };
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
const router = new MessageRouter({
|
|
88
|
+
authorizer,
|
|
89
|
+
channelRegistry,
|
|
90
|
+
orchestrator,
|
|
91
|
+
logger,
|
|
92
|
+
fetchBytes: opts.fetchBytes ?? (async () => new Uint8Array([1, 2, 3])),
|
|
93
|
+
...(opts.eventBus ? { eventBus: opts.eventBus } : {}),
|
|
94
|
+
});
|
|
95
|
+
return { router, orchestrator, sent };
|
|
96
|
+
}
|
|
97
|
+
// Flush the microtask queue enough times for the fire-and-forget completion clear
|
|
98
|
+
// (removeReaction → safe → react → safe) to settle before asserting.
|
|
99
|
+
async function flush() {
|
|
100
|
+
for (let i = 0; i < 6; i++)
|
|
101
|
+
await Promise.resolve();
|
|
102
|
+
}
|
|
103
|
+
let ws;
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
ws = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'dab-msg-')));
|
|
106
|
+
});
|
|
107
|
+
afterEach(() => {
|
|
108
|
+
fs.rmSync(ws, { recursive: true, force: true });
|
|
109
|
+
});
|
|
110
|
+
describe('MessageRouter', () => {
|
|
111
|
+
it('ignores a message in an unbound channel (no send)', async () => {
|
|
112
|
+
const { router, orchestrator } = makeDeps({ binding: undefined });
|
|
113
|
+
const { message } = fakeMessage();
|
|
114
|
+
await router.handle(message);
|
|
115
|
+
expect(orchestrator.send).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
it('ignores a bot message', async () => {
|
|
118
|
+
const { router, orchestrator } = makeDeps({ binding: binding(ws) });
|
|
119
|
+
const { message } = fakeMessage({ author: { id: 'bot', bot: true } });
|
|
120
|
+
await router.handle(message);
|
|
121
|
+
expect(orchestrator.send).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
it('denied user → no orchestrator.send, gets an ephemeral notice', async () => {
|
|
124
|
+
const { router, orchestrator } = makeDeps({
|
|
125
|
+
binding: binding(ws),
|
|
126
|
+
auth: { allowed: false, reason: 'No authorized role for this actor (fail-secure).' },
|
|
127
|
+
});
|
|
128
|
+
const { message, replies } = fakeMessage();
|
|
129
|
+
await router.handle(message);
|
|
130
|
+
expect(orchestrator.send).not.toHaveBeenCalled();
|
|
131
|
+
expect(replies).toHaveLength(1);
|
|
132
|
+
expect(replies[0]).toContain('권한이 없습니다');
|
|
133
|
+
});
|
|
134
|
+
it('allowed user → send called with the built TurnInput; accepted turn → ⏳ reaction', async () => {
|
|
135
|
+
const { router, sent } = makeDeps({ binding: binding(ws) });
|
|
136
|
+
const { message, reactions } = fakeMessage({ content: 'do the thing' });
|
|
137
|
+
await router.handle(message);
|
|
138
|
+
expect(sent).toHaveLength(1);
|
|
139
|
+
expect(sent[0]).toMatchObject({ guildId: 'g1', channelId: 'c1', turn: { text: 'do the thing' } });
|
|
140
|
+
expect(reactions).toEqual(['⏳']);
|
|
141
|
+
});
|
|
142
|
+
it('queued send → still ⏳ reaction (the AI is preparing a response)', async () => {
|
|
143
|
+
const { router } = makeDeps({ binding: binding(ws), sendResult: { status: 'queued', queueDepth: 2 } });
|
|
144
|
+
const { message, reactions } = fakeMessage();
|
|
145
|
+
await router.handle(message);
|
|
146
|
+
expect(reactions).toEqual(['⏳']);
|
|
147
|
+
});
|
|
148
|
+
it('clears ⏳ and adds ✅ when the channel emits a result event', async () => {
|
|
149
|
+
const eventBus = new EventBus();
|
|
150
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
151
|
+
const { message, reactions, removed } = fakeMessage();
|
|
152
|
+
await router.handle(message);
|
|
153
|
+
expect(reactions).toEqual(['⏳']); // working indicator on, not yet cleared
|
|
154
|
+
// The session's turn finishes for this channel.
|
|
155
|
+
eventBus.emit('g1', 'c1', { kind: 'result', text: 'done' });
|
|
156
|
+
await flush();
|
|
157
|
+
expect(removed).toEqual(['⏳']); // working reaction removed
|
|
158
|
+
expect(reactions).toEqual(['⏳', '✅']); // done reaction added
|
|
159
|
+
});
|
|
160
|
+
it('clears ⏳ and adds ❌ when the channel emits an error event', async () => {
|
|
161
|
+
const eventBus = new EventBus();
|
|
162
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
163
|
+
const { message, reactions, removed } = fakeMessage();
|
|
164
|
+
await router.handle(message);
|
|
165
|
+
eventBus.emit('g1', 'c1', { kind: 'error', message: 'boom', retryable: true });
|
|
166
|
+
await flush();
|
|
167
|
+
expect(removed).toEqual(['⏳']);
|
|
168
|
+
expect(reactions).toEqual(['⏳', '❌']);
|
|
169
|
+
});
|
|
170
|
+
it('clears the indicator only ONCE (one-shot): a second result does not re-clear', async () => {
|
|
171
|
+
const eventBus = new EventBus();
|
|
172
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
173
|
+
const { message, reactions, removed } = fakeMessage();
|
|
174
|
+
await router.handle(message);
|
|
175
|
+
eventBus.emit('g1', 'c1', { kind: 'result', text: 'first' });
|
|
176
|
+
await flush();
|
|
177
|
+
eventBus.emit('g1', 'c1', { kind: 'result', text: 'second' });
|
|
178
|
+
await flush();
|
|
179
|
+
expect(removed).toEqual(['⏳']); // removed exactly once
|
|
180
|
+
expect(reactions).toEqual(['⏳', '✅']); // ✅ added exactly once
|
|
181
|
+
});
|
|
182
|
+
it('a non-terminal event (text delta) does NOT clear the indicator', async () => {
|
|
183
|
+
const eventBus = new EventBus();
|
|
184
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
185
|
+
const { message, reactions, removed } = fakeMessage();
|
|
186
|
+
await router.handle(message);
|
|
187
|
+
eventBus.emit('g1', 'c1', { kind: 'text', text: 'partial…', delta: true });
|
|
188
|
+
await flush();
|
|
189
|
+
expect(removed).toEqual([]); // still working
|
|
190
|
+
expect(reactions).toEqual(['⏳']);
|
|
191
|
+
});
|
|
192
|
+
it('a reaction failure never throws (missing Add-Reactions permission)', async () => {
|
|
193
|
+
const eventBus = new EventBus();
|
|
194
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
195
|
+
const { message } = fakeMessage({ reactFails: true });
|
|
196
|
+
// Adding ⏳ throws inside the router; it must be swallowed by safe().
|
|
197
|
+
await expect(router.handle(message)).resolves.toBeUndefined();
|
|
198
|
+
// The completion path also swallows the react failure.
|
|
199
|
+
expect(() => eventBus.emit('g1', 'c1', { kind: 'result', text: 'done' })).not.toThrow();
|
|
200
|
+
await flush();
|
|
201
|
+
});
|
|
202
|
+
it('clears without removeReaction present (bare message): still adds ✅', async () => {
|
|
203
|
+
const eventBus = new EventBus();
|
|
204
|
+
const { router } = makeDeps({ binding: binding(ws), eventBus });
|
|
205
|
+
const { message, reactions } = fakeMessage({ withRemove: false });
|
|
206
|
+
await router.handle(message);
|
|
207
|
+
eventBus.emit('g1', 'c1', { kind: 'result', text: 'done' });
|
|
208
|
+
await flush();
|
|
209
|
+
// No removeReaction to call, but the terminal ✅ is still added.
|
|
210
|
+
expect(reactions).toEqual(['⏳', '✅']);
|
|
211
|
+
});
|
|
212
|
+
it('downloads an attachment INTO the workspace and passes its confined path', async () => {
|
|
213
|
+
const bytes = new Uint8Array([9, 9, 9]);
|
|
214
|
+
const { router, sent } = makeDeps({ binding: binding(ws), fetchBytes: async () => bytes });
|
|
215
|
+
const { message } = fakeMessage({
|
|
216
|
+
attachments: [{ url: 'https://cdn/x', name: 'note.txt', contentType: 'text/plain' }],
|
|
217
|
+
});
|
|
218
|
+
await router.handle(message);
|
|
219
|
+
expect(sent).toHaveLength(1);
|
|
220
|
+
const file = sent[0].turn.files?.[0];
|
|
221
|
+
expect(file).toBeDefined();
|
|
222
|
+
// The saved path is inside the workspace root (confinement precondition).
|
|
223
|
+
const rel = path.relative(ws, file.path);
|
|
224
|
+
expect(rel.startsWith('..')).toBe(false);
|
|
225
|
+
expect(path.isAbsolute(rel)).toBe(false);
|
|
226
|
+
expect(fs.readFileSync(file.path)).toEqual(Buffer.from(bytes));
|
|
227
|
+
expect(file.mime).toBe('text/plain');
|
|
228
|
+
});
|
|
229
|
+
it('a crafted traversal filename is reduced to a safe basename inside the workspace', async () => {
|
|
230
|
+
const { router, sent } = makeDeps({ binding: binding(ws) });
|
|
231
|
+
const { message } = fakeMessage({
|
|
232
|
+
attachments: [{ url: 'https://cdn/evil', name: '../../etc/passwd', contentType: null }],
|
|
233
|
+
});
|
|
234
|
+
await router.handle(message);
|
|
235
|
+
const file = sent[0].turn.files?.[0];
|
|
236
|
+
expect(file).toBeDefined();
|
|
237
|
+
// Written under the attachment dir, not two levels up.
|
|
238
|
+
const rel = path.relative(ws, file.path);
|
|
239
|
+
expect(rel.startsWith('..')).toBe(false);
|
|
240
|
+
expect(path.basename(file.path)).toBe('passwd');
|
|
241
|
+
});
|
|
242
|
+
it('rejects an attachment dir that escapes the workspace (pre-planted symlink)', async () => {
|
|
243
|
+
// Plant a symlink at cwd/.dab-attachments → an OUTSIDE directory. Writing an
|
|
244
|
+
// attachment through it would redirect attacker bytes outside the workspace, so
|
|
245
|
+
// the router must reject before writing.
|
|
246
|
+
const outside = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'dab-outside-')));
|
|
247
|
+
try {
|
|
248
|
+
fs.symlinkSync(outside, path.join(ws, '.dab-attachments'), 'dir');
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// If the platform forbids symlinks, skip — the confinement is still exercised
|
|
252
|
+
// by the traversal-name test above.
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const { router, orchestrator } = makeDeps({ binding: binding(ws) });
|
|
256
|
+
const { message, replies } = fakeMessage({
|
|
257
|
+
attachments: [{ url: 'https://cdn/x', name: 'evil.txt', contentType: 'text/plain' }],
|
|
258
|
+
});
|
|
259
|
+
await router.handle(message);
|
|
260
|
+
// No turn was sent (the download failed the confinement check) and the actor
|
|
261
|
+
// got a notice; nothing was written into the outside directory.
|
|
262
|
+
expect(orchestrator.send).not.toHaveBeenCalled();
|
|
263
|
+
expect(replies.some((r) => r.includes('escapes the workspace'))).toBe(true);
|
|
264
|
+
expect(fs.readdirSync(outside)).toHaveLength(0);
|
|
265
|
+
fs.rmSync(outside, { recursive: true, force: true });
|
|
266
|
+
});
|
|
267
|
+
it('an orchestrator confinement/no-session throw is surfaced as a notice, not a crash', async () => {
|
|
268
|
+
const { router } = makeDeps({
|
|
269
|
+
binding: binding(ws),
|
|
270
|
+
sendThrows: new Error('File path escapes the workspace: /etc/passwd'),
|
|
271
|
+
});
|
|
272
|
+
const { message, replies } = fakeMessage();
|
|
273
|
+
await expect(router.handle(message)).resolves.toBeUndefined();
|
|
274
|
+
expect(replies).toHaveLength(1);
|
|
275
|
+
expect(replies[0]).toContain('escapes the workspace');
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
// A server Administrator can DRIVE a session by messaging even with an EMPTY role
|
|
279
|
+
// allowlist (the fix for the /agent start lockout footgun), while a non-admin with no
|
|
280
|
+
// role stays denied. Uses a REAL Authorizer over a temp config so the admin-tier grant
|
|
281
|
+
// is exercised end-to-end through the router's isAdministrator plumbing.
|
|
282
|
+
describe('MessageRouter Administrator authorization', () => {
|
|
283
|
+
let home;
|
|
284
|
+
function writeEmptyConfig(dir) {
|
|
285
|
+
const config = {
|
|
286
|
+
version: CONFIG_VERSION,
|
|
287
|
+
discord: { token: 'x', clientId: 'cid' },
|
|
288
|
+
auth: { adminRoleIds: [], executeRoleIds: [], readOnlyRoleIds: [], dmPolicy: 'deny' },
|
|
289
|
+
defaults: {
|
|
290
|
+
mode: 'claude',
|
|
291
|
+
claudeModel: 'opus',
|
|
292
|
+
codexModel: '',
|
|
293
|
+
permissionMode: 'default',
|
|
294
|
+
permissionProfile: null,
|
|
295
|
+
codexHome: '~/.codex',
|
|
296
|
+
codexCliCommand: 'codex',
|
|
297
|
+
codexCliVersion: null,
|
|
298
|
+
},
|
|
299
|
+
limits: { maxSessionsPerUser: 0, permissionTimeoutSec: 60, codexTimeoutMs: 1_800_000 },
|
|
300
|
+
policy: { unknownCommand: 'confirm', allowExtraCommands: [] },
|
|
301
|
+
autoAllowClaudeTools: ['Read'],
|
|
302
|
+
profiles: {},
|
|
303
|
+
usage: { userAgent: 'claude-code', cacheSec: 180 },
|
|
304
|
+
audit: { channelId: null },
|
|
305
|
+
locale: 'ko',
|
|
306
|
+
logLevel: 'info',
|
|
307
|
+
favorites: [],
|
|
308
|
+
};
|
|
309
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
310
|
+
fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify(config));
|
|
311
|
+
}
|
|
312
|
+
// A member whose permissions.has(bit) is true iff the requested bit is Administrator.
|
|
313
|
+
const adminMember = {
|
|
314
|
+
roles: { cache: { map: (fn) => [].map(fn) } },
|
|
315
|
+
permissions: { has: (bit) => bit === ADMIN_BIT },
|
|
316
|
+
};
|
|
317
|
+
function realDeps() {
|
|
318
|
+
const store = new ConfigStore(home);
|
|
319
|
+
const registry = new ChannelRegistry(new StateStore(home), () => '2026-01-01T00:00:00.000Z');
|
|
320
|
+
// Bind the channel so the message is treated as a turn.
|
|
321
|
+
registry.set({ guildId: 'g1', channelId: 'c1', mode: 'claude', sessionId: 's1', cwd: ws, ownerId: 'owner', permMode: 'default', profile: null });
|
|
322
|
+
const authorizer = new Authorizer(store, registry);
|
|
323
|
+
const sent = [];
|
|
324
|
+
const orchestrator = {
|
|
325
|
+
send: vi.fn(async (guildId, channelId, turn) => {
|
|
326
|
+
sent.push({ guildId, channelId, turn });
|
|
327
|
+
return { status: 'started', queueDepth: 1 };
|
|
328
|
+
}),
|
|
329
|
+
};
|
|
330
|
+
const router = new MessageRouter({ authorizer, channelRegistry: registry, orchestrator, logger });
|
|
331
|
+
return { router, sent };
|
|
332
|
+
}
|
|
333
|
+
beforeEach(() => {
|
|
334
|
+
home = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-msg-admin-'));
|
|
335
|
+
writeEmptyConfig(home);
|
|
336
|
+
});
|
|
337
|
+
afterEach(() => {
|
|
338
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
339
|
+
});
|
|
340
|
+
it('an Administrator can drive even with an empty role config', async () => {
|
|
341
|
+
const { router, sent } = realDeps();
|
|
342
|
+
const { message, replies } = fakeMessage({ member: adminMember });
|
|
343
|
+
await router.handle(message);
|
|
344
|
+
expect(sent).toHaveLength(1);
|
|
345
|
+
expect(replies).toHaveLength(0); // no denial notice
|
|
346
|
+
});
|
|
347
|
+
it('a non-admin with no role is denied (deny-by-default preserved)', async () => {
|
|
348
|
+
const { router, sent } = realDeps();
|
|
349
|
+
const nonAdminMember = {
|
|
350
|
+
roles: { cache: { map: (fn) => [].map(fn) } },
|
|
351
|
+
permissions: { has: () => false },
|
|
352
|
+
};
|
|
353
|
+
const { message, replies } = fakeMessage({ member: nonAdminMember });
|
|
354
|
+
await router.handle(message);
|
|
355
|
+
expect(sent).toHaveLength(0);
|
|
356
|
+
expect(replies).toHaveLength(1);
|
|
357
|
+
expect(replies[0]).toContain('권한이 없습니다');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
//# sourceMappingURL=messageRouter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messageRouter.test.js","sourceRoot":"","sources":["../../src/discord/messageRouter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Minimal Discord "ports" — the narrow surface the presentation layer (renderers,
|
|
2
|
+
// wizard, browsers) actually uses, decoupled from discord.js. 7b adapts real
|
|
3
|
+
// discord.js objects (TextChannel, Message, ThreadChannel, interactions) onto
|
|
4
|
+
// these interfaces; unit tests supply fakes. Keeping the surface tiny means the
|
|
5
|
+
// renderers stay pure UX logic with no gateway I/O and no discord.js import.
|
|
6
|
+
//
|
|
7
|
+
// These types are LOCAL to the discord layer (contracts.ts stays authoritative
|
|
8
|
+
// for the core seam). See docs/DESIGN.md §6 (rendering layer) and §2 (the Discord
|
|
9
|
+
// layer depends on core contracts only, never the SDK/CLI — here, additionally,
|
|
10
|
+
// not on discord.js directly, so the logic is testable without a live client).
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/discord/ports.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,6EAA6E;AAC7E,8EAA8E;AAC9E,gFAAgF;AAChF,6EAA6E;AAC7E,EAAE;AACF,+EAA+E;AAC/E,kFAAkF;AAClF,gFAAgF;AAChF,+EAA+E"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { RendererDispatcher, createDefaultRendererSet } from './index.js';
|
|
3
|
+
function fakeChannel() {
|
|
4
|
+
const sent = [];
|
|
5
|
+
const edits = [];
|
|
6
|
+
let seq = 0;
|
|
7
|
+
const channel = {
|
|
8
|
+
async send(message) {
|
|
9
|
+
sent.push(message);
|
|
10
|
+
const em = {
|
|
11
|
+
id: `m${++seq}`,
|
|
12
|
+
async edit(m) {
|
|
13
|
+
edits.push(m);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
return em;
|
|
17
|
+
},
|
|
18
|
+
async startThread(name) {
|
|
19
|
+
const id = `t${++seq}`;
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
async send(message) {
|
|
23
|
+
sent.push({ ...message, content: `[thread:${name}] ${message.content ?? ''}` });
|
|
24
|
+
return { id: `tm${++seq}`, async edit() { } };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
return { channel, sent, edits };
|
|
30
|
+
}
|
|
31
|
+
const flush = () => new Promise((r) => setImmediate(r));
|
|
32
|
+
const codexCaps = {
|
|
33
|
+
streaming: false,
|
|
34
|
+
thinking: false,
|
|
35
|
+
toolThreads: false,
|
|
36
|
+
permissionPrompts: false,
|
|
37
|
+
progress: true,
|
|
38
|
+
transcript: true,
|
|
39
|
+
sessionResume: true,
|
|
40
|
+
fileAttach: false,
|
|
41
|
+
fileDiff: false,
|
|
42
|
+
usagePanel: false,
|
|
43
|
+
permissionModes: ['default', 'plan'],
|
|
44
|
+
};
|
|
45
|
+
const claudeCaps = {
|
|
46
|
+
streaming: true,
|
|
47
|
+
thinking: true,
|
|
48
|
+
toolThreads: true,
|
|
49
|
+
permissionPrompts: true,
|
|
50
|
+
progress: false,
|
|
51
|
+
transcript: false,
|
|
52
|
+
sessionResume: true,
|
|
53
|
+
fileAttach: true,
|
|
54
|
+
fileDiff: true,
|
|
55
|
+
usagePanel: true,
|
|
56
|
+
permissionModes: ['default', 'plan'],
|
|
57
|
+
};
|
|
58
|
+
describe('default renderer set — Codex-like routing', () => {
|
|
59
|
+
it('routes final text to a plain channel message and progress to the transcript feed', async () => {
|
|
60
|
+
const { channel, sent } = fakeChannel();
|
|
61
|
+
const set = createDefaultRendererSet({ channel, ownerId: 'u1' });
|
|
62
|
+
const dispatcher = new RendererDispatcher(set, codexCaps);
|
|
63
|
+
dispatcher.dispatch({ kind: 'text', text: 'the answer', delta: false });
|
|
64
|
+
dispatcher.dispatch({ kind: 'progress', label: 'editing file' });
|
|
65
|
+
await flush();
|
|
66
|
+
const contents = sent.map((m) => m.content);
|
|
67
|
+
expect(contents).toContain('the answer'); // plain message, not an embed
|
|
68
|
+
expect(contents).toContain('editing file'); // transcript status line
|
|
69
|
+
expect(sent.every((m) => !m.embeds)).toBe(true); // no streaming embeds for Codex
|
|
70
|
+
});
|
|
71
|
+
it('does not open tool threads or usage panel for Codex caps', async () => {
|
|
72
|
+
const { channel, sent } = fakeChannel();
|
|
73
|
+
const set = createDefaultRendererSet({ channel, ownerId: 'u1' });
|
|
74
|
+
const dispatcher = new RendererDispatcher(set, codexCaps);
|
|
75
|
+
dispatcher.dispatch({ kind: 'tool_use', id: 't1', name: 'Edit', input: {} });
|
|
76
|
+
dispatcher.dispatch({ kind: 'context_usage', totalTokens: 1, maxTokens: 2, percentage: 50 });
|
|
77
|
+
await flush();
|
|
78
|
+
expect(sent.some((m) => (m.content ?? '').startsWith('[thread:'))).toBe(false);
|
|
79
|
+
expect(sent.some((m) => m.embeds?.[0].title?.includes('사용량'))).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('default renderer set — result line + mention', () => {
|
|
83
|
+
it('posts a done-line with only the fields present and @mentions the owner', async () => {
|
|
84
|
+
const { channel, sent } = fakeChannel();
|
|
85
|
+
const set = createDefaultRendererSet({ channel, ownerId: 'owner-9' });
|
|
86
|
+
const dispatcher = new RendererDispatcher(set, claudeCaps);
|
|
87
|
+
dispatcher.dispatch({ kind: 'result', costUsd: 0.1234, durationMs: 3000 });
|
|
88
|
+
await flush();
|
|
89
|
+
const line = sent.find((m) => (m.content ?? '').includes('완료'));
|
|
90
|
+
expect(line?.content).toContain('$0.1234');
|
|
91
|
+
expect(line?.content).toContain('3.0s');
|
|
92
|
+
expect(line?.content).not.toContain('토큰'); // tokens absent → not rendered
|
|
93
|
+
const mention = sent.find((m) => m.content === '<@owner-9>');
|
|
94
|
+
expect(mention?.mentionUserIds).toEqual(['owner-9']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=defaultSet.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultSet.test.js","sourceRoot":"","sources":["../../../src/discord/renderers/defaultSet.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { chunkMessage } from '../format.js';
|
|
2
|
+
// File-change diff view (§6, §5a — Claude, cap fileDiff): when a file-editing tool
|
|
3
|
+
// (Edit/Write/NotebookEdit) completes successfully, post a diff of the change. The
|
|
4
|
+
// diff must be built from the tool INPUT (old/new strings), which lives on the
|
|
5
|
+
// `tool_use` event, not the `tool_result` — so this handler tracks edit inputs by
|
|
6
|
+
// id and renders on the matching successful result (mirrors A4D's changeTracker →
|
|
7
|
+
// diffViewer flow). No discord.js: the sink is the MessageChannel port.
|
|
8
|
+
const FILE_EDIT_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit']);
|
|
9
|
+
export class DiffViewHandler {
|
|
10
|
+
channel;
|
|
11
|
+
// toolUseId → the edit input, kept until the matching result arrives.
|
|
12
|
+
edits = new Map();
|
|
13
|
+
constructor(deps) {
|
|
14
|
+
this.channel = deps.channel;
|
|
15
|
+
}
|
|
16
|
+
// Record a file-edit tool_use so its result can be diffed. Non-edit tools are
|
|
17
|
+
// ignored (nothing tracked → nothing rendered).
|
|
18
|
+
noteToolUse(ev) {
|
|
19
|
+
if (!FILE_EDIT_TOOLS.has(ev.name))
|
|
20
|
+
return;
|
|
21
|
+
const parsed = parseEditInput(ev.input);
|
|
22
|
+
if (parsed)
|
|
23
|
+
this.edits.set(ev.id, parsed);
|
|
24
|
+
}
|
|
25
|
+
// On a successful result for a tracked edit, post its diff. A failed result or an
|
|
26
|
+
// untracked id renders nothing.
|
|
27
|
+
async handleResult(ev) {
|
|
28
|
+
const edit = this.edits.get(ev.id);
|
|
29
|
+
if (!edit)
|
|
30
|
+
return;
|
|
31
|
+
this.edits.delete(ev.id);
|
|
32
|
+
if (!ev.ok)
|
|
33
|
+
return;
|
|
34
|
+
const diff = renderDiff(edit);
|
|
35
|
+
if (!diff)
|
|
36
|
+
return;
|
|
37
|
+
for (const chunk of chunkMessage('```diff\n' + diff + '\n```')) {
|
|
38
|
+
await this.channel.send({ content: chunk });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Build a unified-ish diff body (no headers) from an edit input. Write/create with
|
|
43
|
+
// only `content` renders all lines as additions; an Edit renders old→new.
|
|
44
|
+
function renderDiff(edit) {
|
|
45
|
+
const header = `--- ${edit.filePath}`;
|
|
46
|
+
if (edit.oldString !== undefined || edit.newString !== undefined) {
|
|
47
|
+
const removed = (edit.oldString ?? '').split('\n').map((l) => `- ${l}`);
|
|
48
|
+
const added = (edit.newString ?? '').split('\n').map((l) => `+ ${l}`);
|
|
49
|
+
return [header, ...removed, ...added].join('\n');
|
|
50
|
+
}
|
|
51
|
+
if (edit.content !== undefined) {
|
|
52
|
+
const added = edit.content.split('\n').map((l) => `+ ${l}`);
|
|
53
|
+
return [header, ...added].join('\n');
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function parseEditInput(input) {
|
|
58
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
59
|
+
return null;
|
|
60
|
+
const obj = input;
|
|
61
|
+
const filePath = typeof obj.file_path === 'string' ? obj.file_path : undefined;
|
|
62
|
+
if (!filePath)
|
|
63
|
+
return null;
|
|
64
|
+
const out = { filePath };
|
|
65
|
+
if (typeof obj.old_string === 'string')
|
|
66
|
+
out.oldString = obj.old_string;
|
|
67
|
+
if (typeof obj.new_string === 'string')
|
|
68
|
+
out.newString = obj.new_string;
|
|
69
|
+
if (typeof obj.content === 'string')
|
|
70
|
+
out.content = obj.content;
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=diffView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffView.js","sourceRoot":"","sources":["../../../src/discord/renderers/diffView.ts"],"names":[],"mappings":"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"}
|