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,332 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } 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 { ConfigPanel, isConfigPanelId } from './configPanel.js';
|
|
6
|
+
import { ConfigStore } from '../core/config.js';
|
|
7
|
+
import { permissionModeChoices } from '../core/providerCatalog.js';
|
|
8
|
+
// The /config panel is transport-agnostic (no discord.js): it takes plain inputs
|
|
9
|
+
// (role-select `values`, string-select `value`, Save) and persists to a temp-dir
|
|
10
|
+
// ConfigStore. These tests drive it directly, exactly as the router does.
|
|
11
|
+
const ADMIN = ['role-admin-1'];
|
|
12
|
+
const EXEC = ['role-exec-1'];
|
|
13
|
+
const READ = ['role-read-1'];
|
|
14
|
+
const DEFAULTS = {
|
|
15
|
+
adminRoleIds: ADMIN,
|
|
16
|
+
executeRoleIds: EXEC,
|
|
17
|
+
readOnlyRoleIds: READ,
|
|
18
|
+
backend: 'claude',
|
|
19
|
+
model: 'opus',
|
|
20
|
+
permMode: 'default',
|
|
21
|
+
locale: 'ko',
|
|
22
|
+
};
|
|
23
|
+
let dir;
|
|
24
|
+
let store;
|
|
25
|
+
function makePanel(overrides = {}) {
|
|
26
|
+
return new ConfigPanel({
|
|
27
|
+
guildId: 'g1',
|
|
28
|
+
ownerId: 'admin-user',
|
|
29
|
+
configStore: store,
|
|
30
|
+
defaults: { ...DEFAULTS, ...overrides },
|
|
31
|
+
backends: ['claude', 'codex'],
|
|
32
|
+
// English {value,label} pairs from the provider catalog, exactly as the router
|
|
33
|
+
// supplies them.
|
|
34
|
+
models: [
|
|
35
|
+
{ value: 'opus', label: 'opus' },
|
|
36
|
+
{ value: 'sonnet', label: 'sonnet' },
|
|
37
|
+
],
|
|
38
|
+
permModes: permissionModeChoices('claude'),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-cfgpanel-'));
|
|
43
|
+
store = new ConfigStore(dir);
|
|
44
|
+
});
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
describe('isConfigPanelId', () => {
|
|
49
|
+
it('recognizes config panel component ids and rejects others', () => {
|
|
50
|
+
expect(isConfigPanelId('config.role.admin')).toBe(true);
|
|
51
|
+
expect(isConfigPanelId('config.save')).toBe(true);
|
|
52
|
+
expect(isConfigPanelId('backend')).toBe(false);
|
|
53
|
+
expect(isConfigPanelId('perm:req-1:allow')).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// Small helpers to pull a defaults select and the option marked default-selected.
|
|
57
|
+
function selectById(rows, customId) {
|
|
58
|
+
const found = rows
|
|
59
|
+
.flatMap((r) => r.components)
|
|
60
|
+
.find((c) => c.type === 'select' && c.customId === customId);
|
|
61
|
+
return found && found.type === 'select'
|
|
62
|
+
? found
|
|
63
|
+
: undefined;
|
|
64
|
+
}
|
|
65
|
+
function defaultSelectedValue(select) {
|
|
66
|
+
return select?.options.find((o) => o.default === true)?.value;
|
|
67
|
+
}
|
|
68
|
+
describe('ConfigPanel render', () => {
|
|
69
|
+
it('renders 3 role-selects (prefilled), 4 string-selects, and a Save button — no Codex-path button/modal', () => {
|
|
70
|
+
const { roleRows, defaultRows } = makePanel().render();
|
|
71
|
+
// Flatten to the components in order (both groups form the logical panel).
|
|
72
|
+
const all = [...roleRows, ...defaultRows].flatMap((r) => r.components);
|
|
73
|
+
const roleSelects = all.filter((c) => c.type === 'roleSelect');
|
|
74
|
+
expect(roleSelects).toHaveLength(3);
|
|
75
|
+
// The admin role-select is prefilled with the current admin roles.
|
|
76
|
+
const admin = roleSelects.find((c) => c.customId === 'config.role.admin');
|
|
77
|
+
expect(admin && 'defaultRoleIds' in admin ? admin.defaultRoleIds : undefined).toEqual(ADMIN);
|
|
78
|
+
// backend / model / permMode / locale selects.
|
|
79
|
+
const stringSelects = all.filter((c) => c.type === 'select');
|
|
80
|
+
expect(stringSelects.map((c) => c.customId)).toEqual([
|
|
81
|
+
'config.default.backend',
|
|
82
|
+
'config.default.model',
|
|
83
|
+
'config.default.permMode',
|
|
84
|
+
'config.default.locale',
|
|
85
|
+
]);
|
|
86
|
+
// The roles Save button exists; the Codex-path button/modal are GONE.
|
|
87
|
+
expect(all.some((c) => c.type === 'button' && c.customId === 'config.save')).toBe(true);
|
|
88
|
+
expect(all.some((c) => c.type === 'button' && c.customId === 'config.codexHome.open')).toBe(false);
|
|
89
|
+
// The panel exposes no Codex-home modal any more.
|
|
90
|
+
expect('codexHomeModal' in makePanel()).toBe(false);
|
|
91
|
+
expect('handleCodexHomeSubmit' in makePanel()).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
it('every defaults select marks its CURRENTLY-SAVED value as default-selected', () => {
|
|
94
|
+
const { defaultRows } = makePanel({
|
|
95
|
+
backend: 'codex',
|
|
96
|
+
model: 'sonnet',
|
|
97
|
+
permMode: 'acceptEdits',
|
|
98
|
+
locale: 'en',
|
|
99
|
+
}).render();
|
|
100
|
+
expect(defaultSelectedValue(selectById(defaultRows, 'config.default.backend'))).toBe('codex');
|
|
101
|
+
expect(defaultSelectedValue(selectById(defaultRows, 'config.default.model'))).toBe('sonnet');
|
|
102
|
+
expect(defaultSelectedValue(selectById(defaultRows, 'config.default.permMode'))).toBe('acceptEdits');
|
|
103
|
+
expect(defaultSelectedValue(selectById(defaultRows, 'config.default.locale'))).toBe('en');
|
|
104
|
+
// Exactly ONE option per select is default-selected (no stray defaults).
|
|
105
|
+
for (const id of ['config.default.backend', 'config.default.model', 'config.default.permMode', 'config.default.locale']) {
|
|
106
|
+
const sel = selectById(defaultRows, id);
|
|
107
|
+
expect(sel?.options.filter((o) => o.default === true)).toHaveLength(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
it('regression: the permission-mode select reflects the SAVED default (default), never bypassPermissions', () => {
|
|
111
|
+
// The observed bug: the dropdown pre-selected bypassPermissions while the saved
|
|
112
|
+
// default was `default`. With the current value marked default-selected, the
|
|
113
|
+
// pre-selected option must be `default` and NOT `bypassPermissions`.
|
|
114
|
+
const { defaultRows } = makePanel({ permMode: 'default' }).render();
|
|
115
|
+
const perm = selectById(defaultRows, 'config.default.permMode');
|
|
116
|
+
const def = perm?.options.find((o) => o.value === 'default');
|
|
117
|
+
const bypass = perm?.options.find((o) => o.value === 'bypassPermissions');
|
|
118
|
+
expect(def?.default).toBe(true);
|
|
119
|
+
expect(bypass?.default).toBe(false);
|
|
120
|
+
expect(defaultSelectedValue(perm)).toBe('default');
|
|
121
|
+
// bypassPermissions is only default-selected when the user explicitly picked it.
|
|
122
|
+
const bypassPanel = selectById(makePanel({ permMode: 'bypassPermissions' }).render().defaultRows, 'config.default.permMode');
|
|
123
|
+
expect(defaultSelectedValue(bypassPanel)).toBe('bypassPermissions');
|
|
124
|
+
});
|
|
125
|
+
it('permission-mode + model OPTION labels are the original ENGLISH (no Korean)', () => {
|
|
126
|
+
const { defaultRows } = makePanel().render();
|
|
127
|
+
const perm = selectById(defaultRows, 'config.default.permMode');
|
|
128
|
+
const model = selectById(defaultRows, 'config.default.model');
|
|
129
|
+
// Values are the SDK identifiers; labels are the English identifier + a short
|
|
130
|
+
// English hint — NOT the old Korean strings.
|
|
131
|
+
const byValue = (v) => perm?.options.find((o) => o.value === v);
|
|
132
|
+
expect(byValue('default')?.label).toBe('default (ask each time)');
|
|
133
|
+
expect(byValue('bypassPermissions')?.label).toBe('bypassPermissions (auto-approve all)');
|
|
134
|
+
// A couple of concrete negative assertions: none of the old Korean labels remain.
|
|
135
|
+
const permLabels = perm?.options.map((o) => o.label) ?? [];
|
|
136
|
+
expect(permLabels).not.toContain('기본 (매번 확인)');
|
|
137
|
+
expect(permLabels).not.toContain('전체 자동 승인 (⚠️ 위험)');
|
|
138
|
+
// No option label anywhere in these two selects contains a Hangul character.
|
|
139
|
+
const hangul = /[가-힣]/;
|
|
140
|
+
for (const label of [...permLabels, ...(model?.options.map((o) => o.label) ?? [])]) {
|
|
141
|
+
expect(hangul.test(label)).toBe(false);
|
|
142
|
+
}
|
|
143
|
+
// Claude's full SDK-synced set (incl. dontAsk/auto) is offered.
|
|
144
|
+
expect(perm?.options.map((o) => o.value)).toEqual([
|
|
145
|
+
'default',
|
|
146
|
+
'acceptEdits',
|
|
147
|
+
'bypassPermissions',
|
|
148
|
+
'plan',
|
|
149
|
+
'dontAsk',
|
|
150
|
+
'auto',
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
it('Codex permission-mode options exclude dontAsk/auto (no codex mapping)', () => {
|
|
154
|
+
const codexPanel = new ConfigPanel({
|
|
155
|
+
guildId: 'g1',
|
|
156
|
+
ownerId: 'admin-user',
|
|
157
|
+
configStore: store,
|
|
158
|
+
defaults: { ...DEFAULTS, backend: 'codex' },
|
|
159
|
+
backends: ['claude', 'codex'],
|
|
160
|
+
models: [{ value: 'gpt-5.1-codex', label: 'gpt-5.1-codex' }],
|
|
161
|
+
permModes: permissionModeChoices('codex'),
|
|
162
|
+
});
|
|
163
|
+
const perm = selectById(codexPanel.render().defaultRows, 'config.default.permMode');
|
|
164
|
+
const values = perm?.options.map((o) => o.value);
|
|
165
|
+
expect(values).toEqual(['default', 'acceptEdits', 'bypassPermissions', 'plan']);
|
|
166
|
+
expect(values).not.toContain('dontAsk');
|
|
167
|
+
expect(values).not.toContain('auto');
|
|
168
|
+
});
|
|
169
|
+
it('role selects pre-select the currently-configured roles for every tier', () => {
|
|
170
|
+
const { roleRows } = makePanel().render();
|
|
171
|
+
const roleSelects = roleRows.flatMap((r) => r.components).filter((c) => c.type === 'roleSelect');
|
|
172
|
+
const byId = (id) => roleSelects.find((c) => c.customId === id);
|
|
173
|
+
const admin = byId('config.role.admin');
|
|
174
|
+
const exec = byId('config.role.execute');
|
|
175
|
+
const read = byId('config.role.readOnly');
|
|
176
|
+
expect(admin && 'defaultRoleIds' in admin ? admin.defaultRoleIds : undefined).toEqual(ADMIN);
|
|
177
|
+
expect(exec && 'defaultRoleIds' in exec ? exec.defaultRoleIds : undefined).toEqual(EXEC);
|
|
178
|
+
expect(read && 'defaultRoleIds' in read ? read.defaultRoleIds : undefined).toEqual(READ);
|
|
179
|
+
});
|
|
180
|
+
it('the defaults message stays within Discord’s 5-action-row limit (4 selects, no button)', () => {
|
|
181
|
+
const { roleRows, defaultRows } = makePanel().render();
|
|
182
|
+
// A single Discord message allows at most 5 action rows.
|
|
183
|
+
expect(roleRows.length).toBeLessThanOrEqual(5);
|
|
184
|
+
// Removing the Codex-path button frees a row: the defaults message is now 4 selects.
|
|
185
|
+
expect(defaultRows.length).toBe(4);
|
|
186
|
+
expect(defaultRows.length).toBeLessThanOrEqual(5);
|
|
187
|
+
// The Save button rides with the role tiers (the primary reply); the defaults
|
|
188
|
+
// message auto-saves each field on change, so it carries NO Save button.
|
|
189
|
+
expect(roleRows.flatMap((r) => r.components).some((c) => c.type === 'button' && c.customId === 'config.save')).toBe(true);
|
|
190
|
+
expect(defaultRows.flatMap((r) => r.components).some((c) => c.type === 'button')).toBe(false);
|
|
191
|
+
// No empty rows (Discord rejects them).
|
|
192
|
+
for (const row of [...roleRows, ...defaultRows]) {
|
|
193
|
+
expect(row.components.length).toBeGreaterThan(0);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe('ConfigPanel save', () => {
|
|
198
|
+
it('Save persists picked role ids into servers/<guildId>.json (defaults auto-save separately)', () => {
|
|
199
|
+
const panel = makePanel();
|
|
200
|
+
// Pick new admin + read-only roles via role-selects (batched until Save).
|
|
201
|
+
expect(panel.handle({ id: 'config.role.admin', values: ['new-admin'] })).toEqual({ kind: 'pending' });
|
|
202
|
+
expect(panel.handle({ id: 'config.role.readOnly', values: ['new-read-a', 'new-read-b'] })).toEqual({ kind: 'pending' });
|
|
203
|
+
const result = panel.handle({ id: 'config.save' });
|
|
204
|
+
expect(result.kind).toBe('saved');
|
|
205
|
+
// The server file exists and carries the picked role ids.
|
|
206
|
+
const saved = store.loadServerConfig('g1');
|
|
207
|
+
expect(saved).not.toBeNull();
|
|
208
|
+
expect(saved?.guildId).toBe('g1');
|
|
209
|
+
expect(saved?.auth?.adminRoleIds).toEqual(['new-admin']);
|
|
210
|
+
expect(saved?.auth?.readOnlyRoleIds).toEqual(['new-read-a', 'new-read-b']);
|
|
211
|
+
// The UNTOUCHED execute tier keeps its prior (prefilled) value.
|
|
212
|
+
expect(saved?.auth?.executeRoleIds).toEqual(EXEC);
|
|
213
|
+
});
|
|
214
|
+
it('confirmation summary reflects the picked role mentions and current defaults', () => {
|
|
215
|
+
const panel = makePanel();
|
|
216
|
+
panel.handle({ id: 'config.role.admin', values: ['A1', 'A2'] });
|
|
217
|
+
const result = panel.handle({ id: 'config.save' });
|
|
218
|
+
expect(result.kind).toBe('saved');
|
|
219
|
+
if (result.kind !== 'saved')
|
|
220
|
+
return;
|
|
221
|
+
// Role mentions (<@&id>) for the picked admin roles.
|
|
222
|
+
expect(result.summary).toContain('<@&A1>');
|
|
223
|
+
expect(result.summary).toContain('<@&A2>');
|
|
224
|
+
// Untouched execute tier is still reported (its prior value).
|
|
225
|
+
expect(result.summary).toContain('<@&role-exec-1>');
|
|
226
|
+
// The current default model (the prefill) is reported.
|
|
227
|
+
expect(result.summary).toContain('opus');
|
|
228
|
+
});
|
|
229
|
+
it('an untouched tier keeps the EXISTING server-file value (not the prefill) on Save', () => {
|
|
230
|
+
// Pre-seed a server file with an execute tier different from the panel prefill.
|
|
231
|
+
store.saveServerConfig({
|
|
232
|
+
version: 2,
|
|
233
|
+
guildId: 'g1',
|
|
234
|
+
auth: { adminRoleIds: [], executeRoleIds: ['pre-existing-exec'], readOnlyRoleIds: [] },
|
|
235
|
+
});
|
|
236
|
+
const panel = makePanel();
|
|
237
|
+
// Only touch admin; leave execute + read-only untouched.
|
|
238
|
+
panel.handle({ id: 'config.role.admin', values: ['fresh-admin'] });
|
|
239
|
+
panel.handle({ id: 'config.save' });
|
|
240
|
+
const saved = store.loadServerConfig('g1');
|
|
241
|
+
expect(saved?.auth?.adminRoleIds).toEqual(['fresh-admin']);
|
|
242
|
+
// Untouched execute tier falls through to the existing server-file value.
|
|
243
|
+
expect(saved?.auth?.executeRoleIds).toEqual(['pre-existing-exec']);
|
|
244
|
+
});
|
|
245
|
+
it('a role-select can CLEAR a tier (empty values) — deny-by-default for that tier', () => {
|
|
246
|
+
const panel = makePanel();
|
|
247
|
+
// Explicitly clear the execute tier.
|
|
248
|
+
expect(panel.handle({ id: 'config.role.execute', values: [] })).toEqual({ kind: 'pending' });
|
|
249
|
+
panel.handle({ id: 'config.save' });
|
|
250
|
+
const saved = store.loadServerConfig('g1');
|
|
251
|
+
expect(saved?.auth?.executeRoleIds).toEqual([]);
|
|
252
|
+
// Admin (untouched) keeps its prefill.
|
|
253
|
+
expect(saved?.auth?.adminRoleIds).toEqual(ADMIN);
|
|
254
|
+
});
|
|
255
|
+
it('a malformed role input (no `values`) is ignored, not stored as an empty tier', () => {
|
|
256
|
+
const panel = makePanel();
|
|
257
|
+
expect(panel.handle({ id: 'config.role.admin' })).toEqual({ kind: 'ignored' });
|
|
258
|
+
panel.handle({ id: 'config.save' });
|
|
259
|
+
const saved = store.loadServerConfig('g1');
|
|
260
|
+
// Admin kept its prefill (the glitchy input did not blank it).
|
|
261
|
+
expect(saved?.auth?.adminRoleIds).toEqual(ADMIN);
|
|
262
|
+
});
|
|
263
|
+
it('ignores an unknown component id', () => {
|
|
264
|
+
const panel = makePanel();
|
|
265
|
+
expect(panel.handle({ id: 'config.bogus' })).toEqual({ kind: 'ignored' });
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe('ConfigPanel defaults auto-save (per-field, no Save button)', () => {
|
|
269
|
+
it('a backend select change persists defaults.mode immediately (no Save)', () => {
|
|
270
|
+
const panel = makePanel();
|
|
271
|
+
const result = panel.handle({ id: 'config.default.backend', value: 'codex' });
|
|
272
|
+
expect(result.kind).toBe('autosaved');
|
|
273
|
+
// Persisted right away — no Save call needed.
|
|
274
|
+
expect(store.loadServerConfig('g1')?.defaults?.mode).toBe('codex');
|
|
275
|
+
});
|
|
276
|
+
it('a model select change persists defaults.claudeModel immediately', () => {
|
|
277
|
+
const panel = makePanel();
|
|
278
|
+
const result = panel.handle({ id: 'config.default.model', value: 'sonnet' });
|
|
279
|
+
expect(result.kind).toBe('autosaved');
|
|
280
|
+
expect(store.loadServerConfig('g1')?.defaults?.claudeModel).toBe('sonnet');
|
|
281
|
+
});
|
|
282
|
+
it('a permMode select change persists defaults.permissionMode immediately', () => {
|
|
283
|
+
const panel = makePanel();
|
|
284
|
+
const result = panel.handle({ id: 'config.default.permMode', value: 'acceptEdits' });
|
|
285
|
+
expect(result.kind).toBe('autosaved');
|
|
286
|
+
expect(store.loadServerConfig('g1')?.defaults?.permissionMode).toBe('acceptEdits');
|
|
287
|
+
});
|
|
288
|
+
it('auto-save writes ONLY the changed field, preserving the others', () => {
|
|
289
|
+
const panel = makePanel();
|
|
290
|
+
// Change three defaults in sequence; each merges over the prior server file.
|
|
291
|
+
panel.handle({ id: 'config.default.backend', value: 'codex' });
|
|
292
|
+
panel.handle({ id: 'config.default.model', value: 'sonnet' });
|
|
293
|
+
panel.handle({ id: 'config.default.permMode', value: 'plan' });
|
|
294
|
+
const saved = store.loadServerConfig('g1');
|
|
295
|
+
expect(saved?.defaults?.mode).toBe('codex');
|
|
296
|
+
expect(saved?.defaults?.claudeModel).toBe('sonnet');
|
|
297
|
+
expect(saved?.defaults?.permissionMode).toBe('plan');
|
|
298
|
+
// Auto-saving defaults must NOT write any role tiers (those need Save).
|
|
299
|
+
expect(saved?.auth).toBeUndefined();
|
|
300
|
+
});
|
|
301
|
+
it('auto-save preserves role tiers already Saved to the server file', () => {
|
|
302
|
+
// Pre-seed roles as if a prior Save had run.
|
|
303
|
+
store.saveServerConfig({
|
|
304
|
+
version: 2,
|
|
305
|
+
guildId: 'g1',
|
|
306
|
+
auth: { adminRoleIds: ['pre-admin'], executeRoleIds: ['pre-exec'], readOnlyRoleIds: [] },
|
|
307
|
+
});
|
|
308
|
+
const panel = makePanel();
|
|
309
|
+
panel.handle({ id: 'config.default.model', value: 'sonnet' });
|
|
310
|
+
const saved = store.loadServerConfig('g1');
|
|
311
|
+
// The model landed AND the previously-saved roles are untouched.
|
|
312
|
+
expect(saved?.defaults?.claudeModel).toBe('sonnet');
|
|
313
|
+
expect(saved?.auth?.adminRoleIds).toEqual(['pre-admin']);
|
|
314
|
+
expect(saved?.auth?.executeRoleIds).toEqual(['pre-exec']);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
describe('ConfigPanel locale', () => {
|
|
318
|
+
it('a locale select change persists the per-guild locale immediately', () => {
|
|
319
|
+
const panel = makePanel();
|
|
320
|
+
const result = panel.handle({ id: 'config.default.locale', value: 'en' });
|
|
321
|
+
expect(result.kind).toBe('autosaved');
|
|
322
|
+
expect(store.loadServerConfig('g1')?.locale).toBe('en');
|
|
323
|
+
});
|
|
324
|
+
it('the locale notice names the chosen language', () => {
|
|
325
|
+
const panel = makePanel();
|
|
326
|
+
const result = panel.handle({ id: 'config.default.locale', value: 'en' });
|
|
327
|
+
if (result.kind !== 'autosaved')
|
|
328
|
+
throw new Error('expected autosaved');
|
|
329
|
+
expect(result.notice).toContain('English');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
//# sourceMappingURL=configPanel.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configPanel.test.js","sourceRoot":"","sources":["../../src/discord/configPanel.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { t } from './i18n.js';
|
|
5
|
+
// Filesystem folder navigation used by the channel wizard (§9): list dirs, go up,
|
|
6
|
+
// go into, select the current path. By default (no allowedRoots) the admin driver can
|
|
7
|
+
// browse ANYWHERE up to the filesystem root '/', so the session cwd can be a project on
|
|
8
|
+
// any volume (e.g. /Volumes/<other-drive> on macOS): ⬆ up is enabled at every path
|
|
9
|
+
// except '/' itself. When allowedRoots IS supplied, navigation stays CONFINED within
|
|
10
|
+
// them (the original A5 path-escape guard, still used by callers that want a bounded
|
|
11
|
+
// picker). NOTE: this governs only CHOOSING the cwd; the per-session file-access
|
|
12
|
+
// confinement (attach/download realpath-confined to the chosen cwd) is a SEPARATE
|
|
13
|
+
// mechanism and is unaffected. Pure FS + string logic — no discord.js — so it is
|
|
14
|
+
// unit-testable against temp dirs; the render() output is a plain component spec that
|
|
15
|
+
// 7b maps onto discord.js.
|
|
16
|
+
//
|
|
17
|
+
// custom_id scheme (parsed by 7b's interactionRouter):
|
|
18
|
+
// dir:into (string-select; value = child folder name)
|
|
19
|
+
// dir:up (button — go to the parent folder)
|
|
20
|
+
// dir:here (button — select the current folder / Session Start)
|
|
21
|
+
// dir:resume (button — start the resume-session flow for the current folder)
|
|
22
|
+
// dir:create (button — open the create-folder modal in the current folder)
|
|
23
|
+
// cancel (button — cancel the wizard)
|
|
24
|
+
// Discord select limits (A4D MAX_SELECT_OPTIONS / label length).
|
|
25
|
+
const MAX_SELECT_OPTIONS = 25;
|
|
26
|
+
const MAX_LABEL_LENGTH = 95;
|
|
27
|
+
export class DirectoryBrowser {
|
|
28
|
+
// The confinement roots, or null when the browser is unbounded (browse anywhere up to
|
|
29
|
+
// '/'). Kept separate from `current` so navigation checks pick the right rule.
|
|
30
|
+
roots;
|
|
31
|
+
current;
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
const bounded = Boolean(options.allowedRoots && options.allowedRoots.length > 0);
|
|
34
|
+
this.roots = bounded ? options.allowedRoots.map((r) => path.resolve(r)) : null;
|
|
35
|
+
const fallbackStart = this.roots ? this.roots[0] : os.homedir();
|
|
36
|
+
const start = options.startPath ? path.resolve(options.startPath) : fallbackStart;
|
|
37
|
+
this.current = this.confine(start) ? start : fallbackStart;
|
|
38
|
+
}
|
|
39
|
+
// The folder currently in view.
|
|
40
|
+
cwd() {
|
|
41
|
+
return this.current;
|
|
42
|
+
}
|
|
43
|
+
// Names of immediate subdirectories of the current folder (sorted, capped). Unreadable
|
|
44
|
+
// dirs and non-directory entries are skipped; a permission error yields an empty list.
|
|
45
|
+
listChildren() {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = fs.readdirSync(this.current, { withFileTypes: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return entries
|
|
54
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith('.'))
|
|
55
|
+
.map((e) => e.name)
|
|
56
|
+
.sort((a, b) => a.localeCompare(b))
|
|
57
|
+
.slice(0, MAX_SELECT_OPTIONS);
|
|
58
|
+
}
|
|
59
|
+
// Descend into a child folder. No-ops (returns false) if the child does not exist,
|
|
60
|
+
// is not a directory, or would escape an allowed root.
|
|
61
|
+
into(childName) {
|
|
62
|
+
const target = path.resolve(this.current, childName);
|
|
63
|
+
if (!this.confine(target))
|
|
64
|
+
return false;
|
|
65
|
+
if (!isDirectory(target))
|
|
66
|
+
return false;
|
|
67
|
+
this.current = target;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
// Go up one level. No-ops (returns false) at the filesystem root ('/', where
|
|
71
|
+
// dirname(p) === p) and — in bounded mode — at an allowed-root boundary. In unbounded
|
|
72
|
+
// mode the only stop is '/', so the driver can reach any volume above the start dir.
|
|
73
|
+
up() {
|
|
74
|
+
const parent = path.dirname(this.current);
|
|
75
|
+
if (parent === this.current)
|
|
76
|
+
return false;
|
|
77
|
+
if (!this.confine(parent))
|
|
78
|
+
return false;
|
|
79
|
+
this.current = parent;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// Select the current folder as the session cwd. Returns its absolute path.
|
|
83
|
+
select() {
|
|
84
|
+
return this.current;
|
|
85
|
+
}
|
|
86
|
+
// True when navigation to `target` is permitted. Unbounded mode (roots === null)
|
|
87
|
+
// allows any absolute path (the whole filesystem, up to '/'). Bounded mode requires
|
|
88
|
+
// `target` to be the same as, or nested under, at least one allowed root; path.relative
|
|
89
|
+
// is used so /ws is not fooled by /ws-evil.
|
|
90
|
+
confine(target) {
|
|
91
|
+
const resolved = path.resolve(target);
|
|
92
|
+
if (this.roots === null)
|
|
93
|
+
return true;
|
|
94
|
+
return this.roots.some((root) => isWithin(root, resolved));
|
|
95
|
+
}
|
|
96
|
+
// Build the interactive spec (embed + component rows) for the current view, A4D-style:
|
|
97
|
+
// the embed shows the CURRENT path + how-to guidance, a select menu lists subfolders,
|
|
98
|
+
// and a single action row mirrors A4D's folder-step buttons —
|
|
99
|
+
// ⬆ 상위(Parent) · ✅ 시작(Session Start) · 세션 재개(Resume) · 📁 폴더 만들기(Create) ·
|
|
100
|
+
// 취소(Cancel) — driving navigation, selection, resume, create, and cancel. Two rows
|
|
101
|
+
// total (select + buttons), well within Discord's 5-row-per-message limit. Pure data;
|
|
102
|
+
// 7b turns it into a discord.js reply/update.
|
|
103
|
+
render() {
|
|
104
|
+
const children = this.listChildren();
|
|
105
|
+
const embed = {
|
|
106
|
+
title: t('wizard.step.folder'),
|
|
107
|
+
description: t('dir.guide') + '\n\n' + t('dir.current') + ': `' + this.current + '`',
|
|
108
|
+
};
|
|
109
|
+
const select = {
|
|
110
|
+
type: 'select',
|
|
111
|
+
customId: 'dir:into',
|
|
112
|
+
placeholder: children.length > 0 ? t('dir.select') : t('dir.empty'),
|
|
113
|
+
options: children.length > 0
|
|
114
|
+
? children.map((name) => ({ label: clip(name, MAX_LABEL_LENGTH), value: name }))
|
|
115
|
+
: [{ label: t('dir.empty'), value: '__none__' }],
|
|
116
|
+
};
|
|
117
|
+
const rows = [
|
|
118
|
+
{ components: [select] },
|
|
119
|
+
{
|
|
120
|
+
components: [
|
|
121
|
+
{ type: 'button', customId: 'dir:up', label: t('dir.up'), style: 'secondary', disabled: !this.canGoUp() },
|
|
122
|
+
{ type: 'button', customId: 'dir:here', label: t('dir.here'), style: 'success' },
|
|
123
|
+
{ type: 'button', customId: 'dir:resume', label: t('dir.resume'), style: 'primary' },
|
|
124
|
+
{ type: 'button', customId: 'dir:create', label: t('dir.create'), style: 'secondary' },
|
|
125
|
+
{ type: 'button', customId: 'cancel', label: t('wizard.cancel'), style: 'secondary' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
return { embed, rows };
|
|
130
|
+
}
|
|
131
|
+
canGoUp() {
|
|
132
|
+
const parent = path.dirname(this.current);
|
|
133
|
+
return parent !== this.current && this.confine(parent);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function isDirectory(p) {
|
|
137
|
+
try {
|
|
138
|
+
return fs.statSync(p).isDirectory();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isWithin(root, child) {
|
|
145
|
+
const rel = path.relative(root, child);
|
|
146
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
147
|
+
}
|
|
148
|
+
function clip(s, max) {
|
|
149
|
+
return s.length <= max ? s : s.slice(0, max - 1) + '…';
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=directoryBrowser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryBrowser.js","sourceRoot":"","sources":["../../src/discord/directoryBrowser.ts"],"names":[],"mappings":"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"}
|