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,163 @@
|
|
|
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 { DirectoryBrowser } from './directoryBrowser.js';
|
|
6
|
+
let root;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// root/
|
|
9
|
+
// sub/
|
|
10
|
+
// nested/
|
|
11
|
+
// file.txt
|
|
12
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-dir-'));
|
|
13
|
+
fs.mkdirSync(path.join(root, 'sub', 'nested'), { recursive: true });
|
|
14
|
+
fs.writeFileSync(path.join(root, 'file.txt'), 'x');
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
describe('DirectoryBrowser', () => {
|
|
20
|
+
it('lists only subdirectories, sorted, hiding files and dotfiles', () => {
|
|
21
|
+
fs.mkdirSync(path.join(root, '.hidden'));
|
|
22
|
+
fs.mkdirSync(path.join(root, 'aaa'));
|
|
23
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
24
|
+
expect(b.listChildren()).toEqual(['aaa', 'sub']);
|
|
25
|
+
});
|
|
26
|
+
it('descends into a child and back up', () => {
|
|
27
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
28
|
+
expect(b.into('sub')).toBe(true);
|
|
29
|
+
expect(b.cwd()).toBe(path.join(root, 'sub'));
|
|
30
|
+
expect(b.into('nested')).toBe(true);
|
|
31
|
+
expect(b.cwd()).toBe(path.join(root, 'sub', 'nested'));
|
|
32
|
+
expect(b.up()).toBe(true);
|
|
33
|
+
expect(b.cwd()).toBe(path.join(root, 'sub'));
|
|
34
|
+
});
|
|
35
|
+
it('select() returns the current absolute path', () => {
|
|
36
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
37
|
+
b.into('sub');
|
|
38
|
+
expect(b.select()).toBe(path.join(root, 'sub'));
|
|
39
|
+
});
|
|
40
|
+
it('cannot ascend above an allowed root', () => {
|
|
41
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
42
|
+
expect(b.up()).toBe(false); // already at the root boundary
|
|
43
|
+
expect(b.cwd()).toBe(root);
|
|
44
|
+
});
|
|
45
|
+
it('cannot descend outside an allowed root via traversal', () => {
|
|
46
|
+
const b = new DirectoryBrowser({ allowedRoots: [path.join(root, 'sub')], startPath: path.join(root, 'sub') });
|
|
47
|
+
// Attempt to escape up-and-out with a relative component.
|
|
48
|
+
expect(b.into('..')).toBe(false);
|
|
49
|
+
expect(b.cwd()).toBe(path.join(root, 'sub'));
|
|
50
|
+
});
|
|
51
|
+
it('into a nonexistent or non-directory target is a no-op', () => {
|
|
52
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
53
|
+
expect(b.into('does-not-exist')).toBe(false);
|
|
54
|
+
expect(b.into('file.txt')).toBe(false);
|
|
55
|
+
expect(b.cwd()).toBe(root);
|
|
56
|
+
});
|
|
57
|
+
it('clamps a start path outside the roots back to the first root', () => {
|
|
58
|
+
const b = new DirectoryBrowser({ allowedRoots: [path.join(root, 'sub')], startPath: os.tmpdir() });
|
|
59
|
+
expect(b.cwd()).toBe(path.join(root, 'sub'));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('DirectoryBrowser unbounded (Fix 1: reach the filesystem root and other volumes)', () => {
|
|
63
|
+
it('with NO allowedRoots, can navigate all the way UP to the filesystem root "/"', () => {
|
|
64
|
+
const b = new DirectoryBrowser({ startPath: root });
|
|
65
|
+
// Walk up repeatedly; each up() succeeds until we hit '/'.
|
|
66
|
+
let guard = 0;
|
|
67
|
+
while (b.up() && guard < 100)
|
|
68
|
+
guard++;
|
|
69
|
+
expect(b.cwd()).toBe(path.parse(root).root); // '/' on POSIX
|
|
70
|
+
// At the filesystem root, up() is a no-op (dirname('/') === '/').
|
|
71
|
+
expect(b.up()).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it('⬆ up is ENABLED at every path except "/" in the render', () => {
|
|
74
|
+
// Deep-ish start: up is enabled here.
|
|
75
|
+
const deep = new DirectoryBrowser({ startPath: path.join(root, 'sub', 'nested') });
|
|
76
|
+
const upBtn = (rows) => rows.flatMap((r) => r.components).find((c) => c.type === 'button' && c.customId === 'dir:up');
|
|
77
|
+
expect(upBtn(deep.render().rows)?.disabled).toBe(false);
|
|
78
|
+
// At the filesystem root, up is disabled.
|
|
79
|
+
const atRoot = new DirectoryBrowser({ startPath: path.parse(root).root });
|
|
80
|
+
expect(upBtn(atRoot.render().rows)?.disabled).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it('from "/" can enter a top-level directory (the path to other volumes, e.g. /Volumes)', () => {
|
|
83
|
+
const fsRoot = path.parse(root).root; // '/'
|
|
84
|
+
const b = new DirectoryBrowser({ startPath: fsRoot });
|
|
85
|
+
expect(b.cwd()).toBe(fsRoot);
|
|
86
|
+
// "/" lists its top-level entries (on macOS this includes 'Volumes', the mount root
|
|
87
|
+
// for other drives). Entering any of them descends off the root — the exact behavior
|
|
88
|
+
// the user needs to reach a project on another volume.
|
|
89
|
+
const children = b.listChildren();
|
|
90
|
+
expect(children.length).toBeGreaterThan(0);
|
|
91
|
+
const first = children[0];
|
|
92
|
+
expect(b.into(first)).toBe(true);
|
|
93
|
+
expect(b.cwd()).toBe(path.join(fsRoot, first));
|
|
94
|
+
});
|
|
95
|
+
it('bounded mode still cannot ascend past an allowed root (confinement unchanged)', () => {
|
|
96
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
97
|
+
expect(b.up()).toBe(false);
|
|
98
|
+
expect(b.cwd()).toBe(root);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('DirectoryBrowser render (A4D-style folder picker)', () => {
|
|
102
|
+
function selectOf(rows) {
|
|
103
|
+
return rows.flatMap((r) => r.components).find((c) => c.type === 'select' && c.customId === 'dir:into');
|
|
104
|
+
}
|
|
105
|
+
function buttonOf(rows, id) {
|
|
106
|
+
return rows.flatMap((r) => r.components).find((c) => c.type === 'button' && c.customId === id);
|
|
107
|
+
}
|
|
108
|
+
it('shows the CURRENT path + guidance, a subfolder select, and ⬆ up / ✅ start buttons', () => {
|
|
109
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
110
|
+
const { embed, rows } = b.render();
|
|
111
|
+
// The embed carries the how-to guidance and the current absolute path.
|
|
112
|
+
expect(embed.description).toContain('프로젝트 폴더');
|
|
113
|
+
expect(embed.description).toContain(root);
|
|
114
|
+
// Subfolder select lists the child directory as a navigation option.
|
|
115
|
+
const select = selectOf(rows);
|
|
116
|
+
expect(select?.customId).toBe('dir:into');
|
|
117
|
+
expect(select?.options?.map((o) => o.value)).toContain('sub');
|
|
118
|
+
// Up button is DISABLED at a root boundary; the start button is present.
|
|
119
|
+
expect(buttonOf(rows, 'dir:up')?.disabled).toBe(true);
|
|
120
|
+
const start = buttonOf(rows, 'dir:here');
|
|
121
|
+
expect(start).toBeDefined();
|
|
122
|
+
if (start && 'label' in start)
|
|
123
|
+
expect(start.label).toContain('시작');
|
|
124
|
+
});
|
|
125
|
+
it('after navigating INTO a subfolder, the render reflects the new path and enables ⬆ up', () => {
|
|
126
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
127
|
+
expect(b.into('sub')).toBe(true);
|
|
128
|
+
const { embed, rows } = b.render();
|
|
129
|
+
expect(embed.description).toContain(path.join(root, 'sub'));
|
|
130
|
+
// Now inside a subfolder, going up is allowed.
|
|
131
|
+
expect(buttonOf(rows, 'dir:up')?.disabled).toBe(false);
|
|
132
|
+
// 'nested' is offered as the next descent.
|
|
133
|
+
expect(selectOf(rows)?.options?.map((o) => o.value)).toContain('nested');
|
|
134
|
+
});
|
|
135
|
+
it('navigating UP returns to the parent path in the render', () => {
|
|
136
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
137
|
+
b.into('sub');
|
|
138
|
+
expect(b.up()).toBe(true);
|
|
139
|
+
expect(b.render().embed.description).toContain(root);
|
|
140
|
+
});
|
|
141
|
+
it('an empty folder renders a disabled-style placeholder option, not a crash', () => {
|
|
142
|
+
const empty = path.join(root, 'sub', 'nested');
|
|
143
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: empty });
|
|
144
|
+
const select = selectOf(b.render().rows);
|
|
145
|
+
// The single sentinel option (never a real folder) keeps the select non-empty.
|
|
146
|
+
expect(select?.options).toHaveLength(1);
|
|
147
|
+
expect(select?.options?.[0].value).toBe('__none__');
|
|
148
|
+
});
|
|
149
|
+
it('mirrors A4D folder-step buttons: Parent · Start · Resume · 📁 Create · Cancel in ONE row (≤5 rows)', () => {
|
|
150
|
+
const b = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
|
|
151
|
+
const { rows } = b.render();
|
|
152
|
+
// Two rows total (subfolder select + the five-button row) — well under Discord's 5.
|
|
153
|
+
expect(rows.length).toBeLessThanOrEqual(5);
|
|
154
|
+
expect(rows).toHaveLength(2);
|
|
155
|
+
// The subfolder select stays in its own row.
|
|
156
|
+
expect(rows[0].components.every((c) => c.type === 'select')).toBe(true);
|
|
157
|
+
// The button row carries exactly the five A4D-style buttons, in order.
|
|
158
|
+
const buttonRow = rows[1].components;
|
|
159
|
+
expect(buttonRow.every((c) => c.type === 'button')).toBe(true);
|
|
160
|
+
expect(buttonRow.map((c) => c.customId)).toEqual(['dir:up', 'dir:here', 'dir:resume', 'dir:create', 'cancel']);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=directoryBrowser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryBrowser.test.js","sourceRoot":"","sources":["../../src/discord/directoryBrowser.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// TODO(Phase 1): project favorites/bookmarks (saved cwd paths) (§4, §8).
|
|
2
|
+
export class Favorites {
|
|
3
|
+
list(_guildId) {
|
|
4
|
+
throw new Error('not implemented');
|
|
5
|
+
}
|
|
6
|
+
add(_guildId, _path) {
|
|
7
|
+
throw new Error('not implemented');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=favorites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"favorites.js","sourceRoot":"","sources":["../../src/discord/favorites.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,OAAO,SAAS;IACpB,IAAI,CAAC,QAAgB;QACnB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,QAAgB,EAAE,KAAa;QACjC,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
// Read-only file browse/download, realpath-confined to the session workspace
|
|
4
|
+
// (§4, §7.5, fixes A5). Every browsed/downloaded path is realpath-resolved and
|
|
5
|
+
// must stay inside the workspace root — a symlink pointing outside the root is
|
|
6
|
+
// caught, not just a literal `..`. Reuses the confinement approach from
|
|
7
|
+
// mcpFileTool/sessionOrchestrator (realpath the deepest existing ancestor).
|
|
8
|
+
//
|
|
9
|
+
// This is a "read" component: it lists entries and resolves a download path, but
|
|
10
|
+
// never mutates the filesystem. The actual Discord attachment is built by 7b from
|
|
11
|
+
// the OutgoingFile this returns. Pure FS logic — unit-testable with temp dirs.
|
|
12
|
+
export class WorkspaceEscapeError extends Error {
|
|
13
|
+
requestedPath;
|
|
14
|
+
constructor(requestedPath) {
|
|
15
|
+
super(`Path escapes the workspace: ${requestedPath}`);
|
|
16
|
+
this.requestedPath = requestedPath;
|
|
17
|
+
this.name = 'WorkspaceEscapeError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class FileDownload {
|
|
21
|
+
root;
|
|
22
|
+
constructor(workspaceRoot) {
|
|
23
|
+
this.root = realpathOrResolve(workspaceRoot);
|
|
24
|
+
}
|
|
25
|
+
// List the entries of a directory inside the workspace. `relativeDir` is resolved
|
|
26
|
+
// against the workspace root; anything escaping the root throws WorkspaceEscapeError.
|
|
27
|
+
browse(relativeDir = '.') {
|
|
28
|
+
const target = this.resolveConfined(relativeDir);
|
|
29
|
+
const entries = fs.readdirSync(target, { withFileTypes: true });
|
|
30
|
+
return entries
|
|
31
|
+
.filter((e) => !e.name.startsWith('.'))
|
|
32
|
+
.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }))
|
|
33
|
+
.sort((a, b) => {
|
|
34
|
+
if (a.isDirectory !== b.isDirectory)
|
|
35
|
+
return a.isDirectory ? -1 : 1;
|
|
36
|
+
return a.name.localeCompare(b.name);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Resolve a download target inside the workspace, returning an OutgoingFile that
|
|
40
|
+
// 7b turns into a discord.js attachment. Throws WorkspaceEscapeError when the path
|
|
41
|
+
// escapes the workspace, or a plain Error when the target is missing / not a file.
|
|
42
|
+
download(relativePath) {
|
|
43
|
+
const target = this.resolveConfined(relativePath);
|
|
44
|
+
let stat;
|
|
45
|
+
try {
|
|
46
|
+
stat = fs.statSync(target);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new Error(`File not found: ${relativePath}`);
|
|
50
|
+
}
|
|
51
|
+
if (!stat.isFile()) {
|
|
52
|
+
throw new Error(`Not a file: ${relativePath}`);
|
|
53
|
+
}
|
|
54
|
+
return { path: target, name: path.basename(target) };
|
|
55
|
+
}
|
|
56
|
+
// Resolve `requested` against the workspace root, realpath-confining it. Throws
|
|
57
|
+
// WorkspaceEscapeError if the resolved path is outside the root.
|
|
58
|
+
resolveConfined(requested) {
|
|
59
|
+
const resolved = realpathOrResolve(path.resolve(this.root, requested));
|
|
60
|
+
if (!isWithin(this.root, resolved)) {
|
|
61
|
+
throw new WorkspaceEscapeError(requested);
|
|
62
|
+
}
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Realpath a path, falling back to the realpath of its deepest existing ancestor
|
|
67
|
+
// joined with the non-existent tail — so confinement holds for paths that do not
|
|
68
|
+
// exist yet while still resolving symlinks in the part that does. (Same approach as
|
|
69
|
+
// core/sessionOrchestrator realpathOrResolve; duplicated locally to keep the
|
|
70
|
+
// discord layer free of a core import for a tiny helper.)
|
|
71
|
+
function realpathOrResolve(target) {
|
|
72
|
+
const abs = path.resolve(target);
|
|
73
|
+
let existing = abs;
|
|
74
|
+
const tail = [];
|
|
75
|
+
while (!fs.existsSync(existing)) {
|
|
76
|
+
const parent = path.dirname(existing);
|
|
77
|
+
if (parent === existing)
|
|
78
|
+
break;
|
|
79
|
+
tail.unshift(path.basename(existing));
|
|
80
|
+
existing = parent;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const realExisting = fs.realpathSync(existing);
|
|
84
|
+
return tail.length > 0 ? path.join(realExisting, ...tail) : realExisting;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return abs;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isWithin(root, child) {
|
|
91
|
+
const rel = path.relative(root, child);
|
|
92
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=fileDownload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileDownload.js","sourceRoot":"","sources":["../../src/discord/fileDownload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { FileDownload, WorkspaceEscapeError } from './fileDownload.js';
|
|
6
|
+
let root;
|
|
7
|
+
let outside;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-ws-'));
|
|
10
|
+
outside = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-out-'));
|
|
11
|
+
fs.mkdirSync(path.join(root, 'src'));
|
|
12
|
+
fs.writeFileSync(path.join(root, 'src', 'a.ts'), 'export const a = 1;');
|
|
13
|
+
fs.writeFileSync(path.join(outside, 'secret.txt'), 'nope');
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
17
|
+
fs.rmSync(outside, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
describe('FileDownload', () => {
|
|
20
|
+
it('downloads an in-workspace file', () => {
|
|
21
|
+
const dl = new FileDownload(root);
|
|
22
|
+
const file = dl.download('src/a.ts');
|
|
23
|
+
expect(file.path).toBe(fs.realpathSync(path.join(root, 'src', 'a.ts')));
|
|
24
|
+
expect(file.name).toBe('a.ts');
|
|
25
|
+
});
|
|
26
|
+
it('browses an in-workspace directory (dirs first, dotfiles hidden)', () => {
|
|
27
|
+
fs.mkdirSync(path.join(root, '.git'));
|
|
28
|
+
const dl = new FileDownload(root);
|
|
29
|
+
const entries = dl.browse('.');
|
|
30
|
+
expect(entries.map((e) => e.name)).toEqual(['src']);
|
|
31
|
+
expect(entries[0].isDirectory).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('rejects a path escaping the workspace via ..', () => {
|
|
34
|
+
const dl = new FileDownload(root);
|
|
35
|
+
const rel = path.join('..', path.basename(outside), 'secret.txt');
|
|
36
|
+
expect(() => dl.download(rel)).toThrow(WorkspaceEscapeError);
|
|
37
|
+
});
|
|
38
|
+
it('rejects an absolute path outside the workspace', () => {
|
|
39
|
+
const dl = new FileDownload(root);
|
|
40
|
+
expect(() => dl.download(path.join(outside, 'secret.txt'))).toThrow(WorkspaceEscapeError);
|
|
41
|
+
});
|
|
42
|
+
it('rejects a symlink that points outside the workspace', () => {
|
|
43
|
+
const link = path.join(root, 'escape');
|
|
44
|
+
try {
|
|
45
|
+
fs.symlinkSync(outside, link);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return; // symlinks unsupported on this platform — skip
|
|
49
|
+
}
|
|
50
|
+
const dl = new FileDownload(root);
|
|
51
|
+
expect(() => dl.download(path.join('escape', 'secret.txt'))).toThrow(WorkspaceEscapeError);
|
|
52
|
+
});
|
|
53
|
+
it('throws a plain error for a missing file or a directory target', () => {
|
|
54
|
+
const dl = new FileDownload(root);
|
|
55
|
+
expect(() => dl.download('src/missing.ts')).toThrow(/not found/i);
|
|
56
|
+
expect(() => dl.download('src')).toThrow(/not a file/i);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=fileDownload.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileDownload.test.js","sourceRoot":"","sources":["../../src/discord/fileDownload.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Small formatting helpers shared by the renderers. Pure functions — no discord.js,
|
|
2
|
+
// no I/O — so they are trivially unit-testable. Discord's hard limits (2000 chars
|
|
3
|
+
// per message, 4096 per embed description) are respected here in one place.
|
|
4
|
+
// Discord hard limits.
|
|
5
|
+
export const MSG_LIMIT = 2000; // plain message content
|
|
6
|
+
export const EMBED_DESC_LIMIT = 4096; // embed description
|
|
7
|
+
export const THREAD_NAME_LIMIT = 100; // thread name
|
|
8
|
+
// Embed colors (ported from A4D COLORS; adapted names).
|
|
9
|
+
export const COLORS = {
|
|
10
|
+
streaming: 0xfee75c, // yellow
|
|
11
|
+
thinking: 0x9b59b6, // purple
|
|
12
|
+
permission: 0xe67e22, // orange
|
|
13
|
+
idle: 0x57f287, // green
|
|
14
|
+
error: 0xed4245, // red
|
|
15
|
+
stopped: 0xed4245, // red
|
|
16
|
+
};
|
|
17
|
+
// Split text into Discord-message-sized chunks, preferring to break on newlines so
|
|
18
|
+
// code fences and paragraphs are not cut mid-line where avoidable. Never returns an
|
|
19
|
+
// empty array for a non-empty input; an empty input yields [].
|
|
20
|
+
export function chunkMessage(text, limit = MSG_LIMIT) {
|
|
21
|
+
if (text.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
if (text.length <= limit)
|
|
24
|
+
return [text];
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let rest = text;
|
|
27
|
+
while (rest.length > limit) {
|
|
28
|
+
// Prefer the last newline within the limit; fall back to a hard cut.
|
|
29
|
+
const window = rest.slice(0, limit);
|
|
30
|
+
const nl = window.lastIndexOf('\n');
|
|
31
|
+
const cut = nl > 0 ? nl : limit;
|
|
32
|
+
chunks.push(rest.slice(0, cut));
|
|
33
|
+
rest = rest.slice(cut).replace(/^\n/, '');
|
|
34
|
+
}
|
|
35
|
+
if (rest.length > 0)
|
|
36
|
+
chunks.push(rest);
|
|
37
|
+
return chunks;
|
|
38
|
+
}
|
|
39
|
+
// Truncate a string to `max`, appending an ellipsis when it was cut. Used for embed
|
|
40
|
+
// descriptions (live stream preview) and thread names.
|
|
41
|
+
export function truncate(text, max) {
|
|
42
|
+
if (text.length <= max)
|
|
43
|
+
return text;
|
|
44
|
+
if (max <= 1)
|
|
45
|
+
return text.slice(0, max);
|
|
46
|
+
return text.slice(0, max - 1) + '…';
|
|
47
|
+
}
|
|
48
|
+
// A tool-call thread name from the tool + its input (§6). Ports A4D's per-tool
|
|
49
|
+
// summary heuristics; capped to Discord's 100-char thread-name limit.
|
|
50
|
+
export function toolThreadName(toolName, input) {
|
|
51
|
+
const summary = toolSummary(toolName, input);
|
|
52
|
+
const name = summary ? `${toolName}: ${summary}` : toolName;
|
|
53
|
+
return truncate(name, THREAD_NAME_LIMIT);
|
|
54
|
+
}
|
|
55
|
+
// A short, human summary of a tool's input for the thread name. Falls back to a
|
|
56
|
+
// clipped JSON stringify for unknown tools. Never throws on odd input.
|
|
57
|
+
export function toolSummary(toolName, input) {
|
|
58
|
+
const obj = isRecord(input) ? input : {};
|
|
59
|
+
const str = (v) => (typeof v === 'string' ? v : undefined);
|
|
60
|
+
switch (toolName) {
|
|
61
|
+
case 'Edit':
|
|
62
|
+
case 'Write':
|
|
63
|
+
case 'Read':
|
|
64
|
+
case 'NotebookEdit':
|
|
65
|
+
return str(obj.file_path) ?? str(obj.path) ?? '';
|
|
66
|
+
case 'Bash':
|
|
67
|
+
return (str(obj.command) ?? '').slice(0, 60);
|
|
68
|
+
case 'Glob':
|
|
69
|
+
case 'Grep':
|
|
70
|
+
return str(obj.pattern) ?? '';
|
|
71
|
+
case 'Agent':
|
|
72
|
+
case 'Task':
|
|
73
|
+
return str(obj.description) ?? (str(obj.prompt) ?? '').slice(0, 40);
|
|
74
|
+
case 'WebSearch':
|
|
75
|
+
case 'WebFetch':
|
|
76
|
+
return str(obj.query) ?? str(obj.url) ?? '';
|
|
77
|
+
default: {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.stringify(input).slice(0, 60);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Compact token count (1234 → "1.2K", 2_000_000 → "2.0M").
|
|
88
|
+
export function formatTokens(count) {
|
|
89
|
+
if (count >= 1_000_000)
|
|
90
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
91
|
+
if (count >= 1_000)
|
|
92
|
+
return `${(count / 1_000).toFixed(1)}K`;
|
|
93
|
+
return String(count);
|
|
94
|
+
}
|
|
95
|
+
// Human duration: >=60s → "1.5m", else "12.3s".
|
|
96
|
+
export function formatDuration(ms) {
|
|
97
|
+
if (ms >= 60_000)
|
|
98
|
+
return `${(ms / 60_000).toFixed(1)}m`;
|
|
99
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
100
|
+
}
|
|
101
|
+
function isRecord(v) {
|
|
102
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/discord/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { chunkMessage, truncate, toolThreadName, formatTokens, formatDuration, MSG_LIMIT, THREAD_NAME_LIMIT, } from './format.js';
|
|
3
|
+
describe('format helpers', () => {
|
|
4
|
+
it('chunkMessage keeps short text whole and splits long text under the limit', () => {
|
|
5
|
+
expect(chunkMessage('short')).toEqual(['short']);
|
|
6
|
+
expect(chunkMessage('')).toEqual([]);
|
|
7
|
+
const long = 'a'.repeat(MSG_LIMIT * 2 + 10);
|
|
8
|
+
const chunks = chunkMessage(long);
|
|
9
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
10
|
+
expect(chunks.every((c) => c.length <= MSG_LIMIT)).toBe(true);
|
|
11
|
+
expect(chunks.join('')).toBe(long);
|
|
12
|
+
});
|
|
13
|
+
it('chunkMessage prefers to break on a newline', () => {
|
|
14
|
+
// First line fills nearly the whole limit; total exceeds it so a split is forced,
|
|
15
|
+
// and the break lands on the newline (not mid-line).
|
|
16
|
+
const line = 'x'.repeat(MSG_LIMIT - 5);
|
|
17
|
+
const text = `${line}\n${'y'.repeat(50)}`;
|
|
18
|
+
const chunks = chunkMessage(text);
|
|
19
|
+
expect(chunks[0]).toBe(line);
|
|
20
|
+
expect(chunks[1]).toBe('y'.repeat(50));
|
|
21
|
+
});
|
|
22
|
+
it('truncate appends an ellipsis only when cut', () => {
|
|
23
|
+
expect(truncate('abc', 5)).toBe('abc');
|
|
24
|
+
expect(truncate('abcdef', 4)).toBe('abc…');
|
|
25
|
+
});
|
|
26
|
+
it('toolThreadName summarizes per-tool input and caps to the thread-name limit', () => {
|
|
27
|
+
expect(toolThreadName('Edit', { file_path: '/ws/a.ts' })).toBe('Edit: /ws/a.ts');
|
|
28
|
+
expect(toolThreadName('Bash', { command: 'ls -la' })).toBe('Bash: ls -la');
|
|
29
|
+
const longPath = '/' + 'p'.repeat(200);
|
|
30
|
+
expect(toolThreadName('Read', { file_path: longPath }).length).toBeLessThanOrEqual(THREAD_NAME_LIMIT);
|
|
31
|
+
});
|
|
32
|
+
it('formatTokens / formatDuration are compact', () => {
|
|
33
|
+
expect(formatTokens(512)).toBe('512');
|
|
34
|
+
expect(formatTokens(1500)).toBe('1.5K');
|
|
35
|
+
expect(formatTokens(2_000_000)).toBe('2.0M');
|
|
36
|
+
expect(formatDuration(3000)).toBe('3.0s');
|
|
37
|
+
expect(formatDuration(90_000)).toBe('1.5m');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=format.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/discord/format.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Names for the created structure. Kept here (not in i18n) since they are Discord
|
|
2
|
+
// channel/category names, not localized bot messages — Discord lowercases/sanitizes
|
|
3
|
+
// text channel names itself, but we pass already-clean values.
|
|
4
|
+
const CONTROL_CATEGORY_NAME = '🤖 Agent';
|
|
5
|
+
const CONTROL_CHANNEL_NAME = 'agent-start';
|
|
6
|
+
const SESSIONS_CATEGORY_NAME = 'Agent - Sessions';
|
|
7
|
+
// Idempotently create (or reuse) the guild's channel structure and persist the ids
|
|
8
|
+
// into servers/<guildId>.json. Re-running with an existing, still-valid structure
|
|
9
|
+
// reuses every channel (matched by stored id) instead of duplicating — the ids only
|
|
10
|
+
// change when a channel was deleted out from under us. Returns the resolved structure
|
|
11
|
+
// so the caller can post a summary / link the control channel.
|
|
12
|
+
export async function ensureGuildChannels(provisioner, configStore) {
|
|
13
|
+
const guildId = provisioner.guildId;
|
|
14
|
+
const server = configStore.loadServerConfig(guildId);
|
|
15
|
+
const existing = server?.channels;
|
|
16
|
+
// Reuse each stored id ONLY when the channel still exists; otherwise re-create it.
|
|
17
|
+
const controlCategory = await provisioner.ensureCategory(CONTROL_CATEGORY_NAME, existing && provisioner.channelExists(existing.categoryId) ? existing.categoryId : undefined);
|
|
18
|
+
const controlChannel = await provisioner.ensureTextChannel(CONTROL_CHANNEL_NAME, controlCategory.id, existing && provisioner.channelExists(existing.controlChannelId) ? existing.controlChannelId : undefined);
|
|
19
|
+
const sessionsCategory = await provisioner.ensureCategory(SESSIONS_CATEGORY_NAME, existing && provisioner.channelExists(existing.sessionsCategoryId) ? existing.sessionsCategoryId : undefined);
|
|
20
|
+
const channels = {
|
|
21
|
+
categoryId: controlCategory.id,
|
|
22
|
+
controlChannelId: controlChannel.id,
|
|
23
|
+
sessionsCategoryId: sessionsCategory.id,
|
|
24
|
+
statusChannelId: existing?.statusChannelId ?? null,
|
|
25
|
+
};
|
|
26
|
+
persistChannels(configStore, guildId, server, channels);
|
|
27
|
+
return channels;
|
|
28
|
+
}
|
|
29
|
+
// Auto-provision a guild's channel structure without a manual /init. Called on
|
|
30
|
+
// ClientReady (for every existing guild) and on GuildCreate (a fresh invite), so the
|
|
31
|
+
// 🤖 Agent category + #agent-start control channel + Agent - Sessions category appear
|
|
32
|
+
// automatically. GUARDED and NON-THROWING by design: skips with a clear warning when
|
|
33
|
+
// the bot lacks Manage Channels, and swallows any create failure (a missing permission
|
|
34
|
+
// surfaces as a create error on some paths) so one bad guild never crashes the ready
|
|
35
|
+
// handler. Idempotent — reuses existing channels by stored id via ensureGuildChannels.
|
|
36
|
+
// Returns the resolved structure, or null when it could not provision.
|
|
37
|
+
export async function autoProvisionGuild(provisioner, configStore, logger) {
|
|
38
|
+
if (!provisioner.canManageChannels()) {
|
|
39
|
+
logger.warn('auto-provision skipped: missing Manage Channels permission', {
|
|
40
|
+
guildId: provisioner.guildId,
|
|
41
|
+
});
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const channels = await ensureGuildChannels(provisioner, configStore);
|
|
46
|
+
logger.info('auto-provisioned guild channels', {
|
|
47
|
+
guildId: provisioner.guildId,
|
|
48
|
+
controlChannelId: channels.controlChannelId,
|
|
49
|
+
});
|
|
50
|
+
return channels;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger.warn('auto-provision failed', {
|
|
54
|
+
guildId: provisioner.guildId,
|
|
55
|
+
err: err instanceof Error ? err.message : String(err),
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Create a dedicated session channel for a picked project folder, under the guild's
|
|
61
|
+
// sessions category (from /init) when available. The name is derived from the folder
|
|
62
|
+
// basename, sanitized to Discord's channel-name rules and prefixed `proj-`. Returns
|
|
63
|
+
// the new channel; the caller binds the session to its id.
|
|
64
|
+
export async function createSessionChannel(provisioner, folderPath, sessionsCategoryId) {
|
|
65
|
+
const name = sessionChannelName(folderPath);
|
|
66
|
+
return provisioner.createTextChannel(name, sessionsCategoryId);
|
|
67
|
+
}
|
|
68
|
+
// Derive a Discord-safe session channel name from a folder path: `proj-<basename>`,
|
|
69
|
+
// lowercased, non-alphanumerics collapsed to '-', trimmed, and capped at Discord's
|
|
70
|
+
// 100-char channel-name limit. An empty/blank basename falls back to `proj-session`.
|
|
71
|
+
export function sessionChannelName(folderPath) {
|
|
72
|
+
const base = folderPath.replace(/[/\\]+$/, '').split(/[/\\]/).pop() ?? '';
|
|
73
|
+
const slug = base
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
76
|
+
.replace(/^-+|-+$/g, '');
|
|
77
|
+
const name = slug.length > 0 ? `proj-${slug}` : 'proj-session';
|
|
78
|
+
return name.slice(0, 100);
|
|
79
|
+
}
|
|
80
|
+
// Merge the resolved channel ids into servers/<guildId>.json without disturbing the
|
|
81
|
+
// server's other fields (auth / defaults / locale). A server file may not exist yet
|
|
82
|
+
// (first /init before any /config); create a minimal one carrying just the channels.
|
|
83
|
+
function persistChannels(configStore, guildId, existing, channels) {
|
|
84
|
+
const next = {
|
|
85
|
+
version: existing?.version ?? 1,
|
|
86
|
+
guildId,
|
|
87
|
+
...(existing?.auth ? { auth: existing.auth } : {}),
|
|
88
|
+
...(existing?.defaults ? { defaults: existing.defaults } : {}),
|
|
89
|
+
...(existing?.limits ? { limits: existing.limits } : {}),
|
|
90
|
+
...(existing?.locale ? { locale: existing.locale } : {}),
|
|
91
|
+
...(existing?.auditChannelId !== undefined ? { auditChannelId: existing.auditChannelId } : {}),
|
|
92
|
+
...(existing?.favorites ? { favorites: existing.favorites } : {}),
|
|
93
|
+
channels,
|
|
94
|
+
};
|
|
95
|
+
configStore.saveServerConfig(next);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=guildChannels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guildChannels.js","sourceRoot":"","sources":["../../src/discord/guildChannels.ts"],"names":[],"mappings":"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"}
|