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.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/app.js +210 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/app.test.js +117 -0
  6. package/dist/app.test.js.map +1 -0
  7. package/dist/cli.js +63 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/cli.test.js +91 -0
  10. package/dist/cli.test.js.map +1 -0
  11. package/dist/core/auditLog.js +65 -0
  12. package/dist/core/auditLog.js.map +1 -0
  13. package/dist/core/auditLog.test.js +124 -0
  14. package/dist/core/auditLog.test.js.map +1 -0
  15. package/dist/core/auth.js +106 -0
  16. package/dist/core/auth.js.map +1 -0
  17. package/dist/core/auth.test.js +216 -0
  18. package/dist/core/auth.test.js.map +1 -0
  19. package/dist/core/channelRegistry.js +119 -0
  20. package/dist/core/channelRegistry.js.map +1 -0
  21. package/dist/core/channelRegistry.test.js +114 -0
  22. package/dist/core/channelRegistry.test.js.map +1 -0
  23. package/dist/core/commandPolicy.js +6 -0
  24. package/dist/core/commandPolicy.js.map +1 -0
  25. package/dist/core/commandRouter.js +9 -0
  26. package/dist/core/commandRouter.js.map +1 -0
  27. package/dist/core/config.js +141 -0
  28. package/dist/core/config.js.map +1 -0
  29. package/dist/core/config.test.js +149 -0
  30. package/dist/core/config.test.js.map +1 -0
  31. package/dist/core/configResolver.js +90 -0
  32. package/dist/core/configResolver.js.map +1 -0
  33. package/dist/core/configResolver.test.js +138 -0
  34. package/dist/core/configResolver.test.js.map +1 -0
  35. package/dist/core/configSchema.js +164 -0
  36. package/dist/core/configSchema.js.map +1 -0
  37. package/dist/core/contracts.js +5 -0
  38. package/dist/core/contracts.js.map +1 -0
  39. package/dist/core/eventBus.js +42 -0
  40. package/dist/core/eventBus.js.map +1 -0
  41. package/dist/core/eventBus.test.js +72 -0
  42. package/dist/core/eventBus.test.js.map +1 -0
  43. package/dist/core/hookBridge.js +11 -0
  44. package/dist/core/hookBridge.js.map +1 -0
  45. package/dist/core/logger.js +116 -0
  46. package/dist/core/logger.js.map +1 -0
  47. package/dist/core/logger.test.js +88 -0
  48. package/dist/core/logger.test.js.map +1 -0
  49. package/dist/core/modeRegistry.js +33 -0
  50. package/dist/core/modeRegistry.js.map +1 -0
  51. package/dist/core/permissionResolver.js +43 -0
  52. package/dist/core/permissionResolver.js.map +1 -0
  53. package/dist/core/permissionResolver.test.js +136 -0
  54. package/dist/core/permissionResolver.test.js.map +1 -0
  55. package/dist/core/providerCatalog.js +240 -0
  56. package/dist/core/providerCatalog.js.map +1 -0
  57. package/dist/core/providerCatalog.test.js +230 -0
  58. package/dist/core/providerCatalog.test.js.map +1 -0
  59. package/dist/core/sessionOrchestrator.js +463 -0
  60. package/dist/core/sessionOrchestrator.js.map +1 -0
  61. package/dist/core/sessionOrchestrator.test.js +344 -0
  62. package/dist/core/sessionOrchestrator.test.js.map +1 -0
  63. package/dist/core/state/schema.js +49 -0
  64. package/dist/core/state/schema.js.map +1 -0
  65. package/dist/core/state/store.js +86 -0
  66. package/dist/core/state/store.js.map +1 -0
  67. package/dist/core/state/store.test.js +99 -0
  68. package/dist/core/state/store.test.js.map +1 -0
  69. package/dist/core/usageService.js +310 -0
  70. package/dist/core/usageService.js.map +1 -0
  71. package/dist/core/usageService.test.js +234 -0
  72. package/dist/core/usageService.test.js.map +1 -0
  73. package/dist/discord/alwaysAllow.test.js +181 -0
  74. package/dist/discord/alwaysAllow.test.js.map +1 -0
  75. package/dist/discord/client.js +666 -0
  76. package/dist/discord/client.js.map +1 -0
  77. package/dist/discord/client.test.js +256 -0
  78. package/dist/discord/client.test.js.map +1 -0
  79. package/dist/discord/configPanel.js +270 -0
  80. package/dist/discord/configPanel.js.map +1 -0
  81. package/dist/discord/configPanel.test.js +332 -0
  82. package/dist/discord/configPanel.test.js.map +1 -0
  83. package/dist/discord/directoryBrowser.js +151 -0
  84. package/dist/discord/directoryBrowser.js.map +1 -0
  85. package/dist/discord/directoryBrowser.test.js +163 -0
  86. package/dist/discord/directoryBrowser.test.js.map +1 -0
  87. package/dist/discord/favorites.js +10 -0
  88. package/dist/discord/favorites.js.map +1 -0
  89. package/dist/discord/fileDownload.js +94 -0
  90. package/dist/discord/fileDownload.js.map +1 -0
  91. package/dist/discord/fileDownload.test.js +59 -0
  92. package/dist/discord/fileDownload.test.js.map +1 -0
  93. package/dist/discord/format.js +104 -0
  94. package/dist/discord/format.js.map +1 -0
  95. package/dist/discord/format.test.js +40 -0
  96. package/dist/discord/format.test.js.map +1 -0
  97. package/dist/discord/guildChannels.js +97 -0
  98. package/dist/discord/guildChannels.js.map +1 -0
  99. package/dist/discord/guildChannels.test.js +231 -0
  100. package/dist/discord/guildChannels.test.js.map +1 -0
  101. package/dist/discord/i18n.js +207 -0
  102. package/dist/discord/i18n.js.map +1 -0
  103. package/dist/discord/i18n.test.js +31 -0
  104. package/dist/discord/i18n.test.js.map +1 -0
  105. package/dist/discord/interactionRouter.js +954 -0
  106. package/dist/discord/interactionRouter.js.map +1 -0
  107. package/dist/discord/interactionRouter.test.js +1245 -0
  108. package/dist/discord/interactionRouter.test.js.map +1 -0
  109. package/dist/discord/messageRouter.js +221 -0
  110. package/dist/discord/messageRouter.js.map +1 -0
  111. package/dist/discord/messageRouter.test.js +360 -0
  112. package/dist/discord/messageRouter.test.js.map +1 -0
  113. package/dist/discord/ports.js +12 -0
  114. package/dist/discord/ports.js.map +1 -0
  115. package/dist/discord/renderers/defaultSet.test.js +97 -0
  116. package/dist/discord/renderers/defaultSet.test.js.map +1 -0
  117. package/dist/discord/renderers/diffView.js +73 -0
  118. package/dist/discord/renderers/diffView.js.map +1 -0
  119. package/dist/discord/renderers/diffView.test.js +54 -0
  120. package/dist/discord/renderers/diffView.test.js.map +1 -0
  121. package/dist/discord/renderers/index.js +143 -0
  122. package/dist/discord/renderers/index.js.map +1 -0
  123. package/dist/discord/renderers/index.test.js +124 -0
  124. package/dist/discord/renderers/index.test.js.map +1 -0
  125. package/dist/discord/renderers/mentionOnComplete.js +19 -0
  126. package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
  127. package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
  128. package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
  129. package/dist/discord/renderers/permissionButtons.js +150 -0
  130. package/dist/discord/renderers/permissionButtons.js.map +1 -0
  131. package/dist/discord/renderers/permissionButtons.test.js +108 -0
  132. package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
  133. package/dist/discord/renderers/resultLine.js +25 -0
  134. package/dist/discord/renderers/resultLine.js.map +1 -0
  135. package/dist/discord/renderers/statusEmbed.js +20 -0
  136. package/dist/discord/renderers/statusEmbed.js.map +1 -0
  137. package/dist/discord/renderers/statusEmbed.test.js +32 -0
  138. package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
  139. package/dist/discord/renderers/streamEmbed.js +134 -0
  140. package/dist/discord/renderers/streamEmbed.js.map +1 -0
  141. package/dist/discord/renderers/streamEmbed.test.js +97 -0
  142. package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
  143. package/dist/discord/renderers/toolThread.js +65 -0
  144. package/dist/discord/renderers/toolThread.js.map +1 -0
  145. package/dist/discord/renderers/toolThread.test.js +54 -0
  146. package/dist/discord/renderers/toolThread.test.js.map +1 -0
  147. package/dist/discord/renderers/transcriptFeed.js +33 -0
  148. package/dist/discord/renderers/transcriptFeed.js.map +1 -0
  149. package/dist/discord/renderers/usageEmbed.js +78 -0
  150. package/dist/discord/renderers/usageEmbed.js.map +1 -0
  151. package/dist/discord/renderers/usageEmbed.test.js +42 -0
  152. package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
  153. package/dist/discord/wiring.js +214 -0
  154. package/dist/discord/wiring.js.map +1 -0
  155. package/dist/discord/wiring.test.js +252 -0
  156. package/dist/discord/wiring.test.js.map +1 -0
  157. package/dist/discord/wizard/channelWizard.js +305 -0
  158. package/dist/discord/wizard/channelWizard.js.map +1 -0
  159. package/dist/discord/wizard/channelWizard.test.js +298 -0
  160. package/dist/discord/wizard/channelWizard.test.js.map +1 -0
  161. package/dist/discord/wizard/resumeWizard.js +123 -0
  162. package/dist/discord/wizard/resumeWizard.js.map +1 -0
  163. package/dist/modes/claude/index.js +80 -0
  164. package/dist/modes/claude/index.js.map +1 -0
  165. package/dist/modes/claude/listResumable.test.js +72 -0
  166. package/dist/modes/claude/listResumable.test.js.map +1 -0
  167. package/dist/modes/claude/mcpFileTool.js +82 -0
  168. package/dist/modes/claude/mcpFileTool.js.map +1 -0
  169. package/dist/modes/claude/permissions.js +35 -0
  170. package/dist/modes/claude/permissions.js.map +1 -0
  171. package/dist/modes/claude/plugins.js +56 -0
  172. package/dist/modes/claude/plugins.js.map +1 -0
  173. package/dist/modes/claude/session.js +324 -0
  174. package/dist/modes/claude/session.js.map +1 -0
  175. package/dist/modes/claude/session.test.js +415 -0
  176. package/dist/modes/claude/session.test.js.map +1 -0
  177. package/dist/modes/codex/discovery.js +221 -0
  178. package/dist/modes/codex/discovery.js.map +1 -0
  179. package/dist/modes/codex/discovery.test.js +213 -0
  180. package/dist/modes/codex/discovery.test.js.map +1 -0
  181. package/dist/modes/codex/eventMapper.js +262 -0
  182. package/dist/modes/codex/eventMapper.js.map +1 -0
  183. package/dist/modes/codex/index.js +132 -0
  184. package/dist/modes/codex/index.js.map +1 -0
  185. package/dist/modes/codex/index.test.js +170 -0
  186. package/dist/modes/codex/index.test.js.map +1 -0
  187. package/dist/modes/codex/runner.js +207 -0
  188. package/dist/modes/codex/runner.js.map +1 -0
  189. package/dist/modes/codex/runner.test.js +587 -0
  190. package/dist/modes/codex/runner.test.js.map +1 -0
  191. package/dist/modes/codex/sqliteReader.js +171 -0
  192. package/dist/modes/codex/sqliteReader.js.map +1 -0
  193. package/dist/modes/codex/sqliteReader.test.js +137 -0
  194. package/dist/modes/codex/sqliteReader.test.js.map +1 -0
  195. package/dist/setup/wizard.js +115 -0
  196. package/dist/setup/wizard.js.map +1 -0
  197. package/dist/setup/wizard.test.js +216 -0
  198. package/dist/setup/wizard.test.js.map +1 -0
  199. package/dist/startBot.test.js +78 -0
  200. package/dist/startBot.test.js.map +1 -0
  201. package/package.json +58 -0
@@ -0,0 +1,344 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { ConfigStore } from './config.js';
6
+ import { CONFIG_VERSION } from './configSchema.js';
7
+ import { StateStore } from './state/store.js';
8
+ import { ChannelRegistry } from './channelRegistry.js';
9
+ import { ConfigResolver } from './configResolver.js';
10
+ import { PermissionResolver } from './permissionResolver.js';
11
+ import { AuditLog } from './auditLog.js';
12
+ import { EventBus } from './eventBus.js';
13
+ import { ModeRegistry } from './modeRegistry.js';
14
+ import { createLogger } from './logger.js';
15
+ import { SessionOrchestrator } from './sessionOrchestrator.js';
16
+ const CLAUDE_CAPS = {
17
+ streaming: true,
18
+ thinking: true,
19
+ toolThreads: true,
20
+ permissionPrompts: true,
21
+ progress: false,
22
+ transcript: false,
23
+ sessionResume: true,
24
+ fileAttach: true,
25
+ fileDiff: true,
26
+ usagePanel: true,
27
+ permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
28
+ };
29
+ // A mock ModeSession that records the turns it received (in order) and emits a
30
+ // scripted event on each send/stop. A `gate` lets a test hold the FIRST send
31
+ // "running" so a second send() must queue behind it — exercising the A4 queue.
32
+ class MockSession {
33
+ sessionId;
34
+ turns = [];
35
+ stopped = false;
36
+ ctx;
37
+ gate;
38
+ gateUsed = false;
39
+ constructor(ctx, sessionId, gate) {
40
+ this.ctx = ctx;
41
+ this.sessionId = sessionId;
42
+ this.gate = gate;
43
+ }
44
+ async send(turn) {
45
+ // The first send awaits the gate (if any) while later turns proceed at once,
46
+ // so a test can assert a mid-turn send() is queued, not dropped.
47
+ if (this.gate && !this.gateUsed) {
48
+ this.gateUsed = true;
49
+ await this.gate;
50
+ }
51
+ this.turns.push(turn);
52
+ this.ctx.emit({ kind: 'text', text: turn.text, delta: false });
53
+ }
54
+ async stop() {
55
+ this.stopped = true;
56
+ }
57
+ }
58
+ // A mock AgentMode that records ctx it was started/resumed with, and hands back
59
+ // a MockSession. `resumeThrows` makes resume() fail so resumeAll() tolerance is
60
+ // testable. `gate` is threaded to the session created by start().
61
+ class MockMode {
62
+ name;
63
+ opts;
64
+ capabilities = CLAUDE_CAPS;
65
+ startedCtx = [];
66
+ resumedCtx = [];
67
+ resumedIds = [];
68
+ lastSession = null;
69
+ constructor(name, opts = {}) {
70
+ this.name = name;
71
+ this.opts = opts;
72
+ }
73
+ async start(ctx) {
74
+ this.startedCtx.push(ctx);
75
+ const session = new MockSession(ctx, `sess-${ctx.channelId}`, this.opts.gate);
76
+ this.lastSession = session;
77
+ return session;
78
+ }
79
+ async resume(ctx, sessionId) {
80
+ if (this.opts.resumeThrows)
81
+ throw new Error('resume boom');
82
+ this.resumedCtx.push(ctx);
83
+ this.resumedIds.push(sessionId);
84
+ const session = new MockSession(ctx, sessionId);
85
+ this.lastSession = session;
86
+ return session;
87
+ }
88
+ }
89
+ function makeConfig() {
90
+ return {
91
+ version: CONFIG_VERSION,
92
+ discord: { token: 'placeholder-token', clientId: '000000000' },
93
+ auth: { adminRoleIds: [], executeRoleIds: [], readOnlyRoleIds: [], dmPolicy: 'deny' },
94
+ defaults: {
95
+ mode: 'claude',
96
+ claudeModel: 'opus',
97
+ codexModel: '',
98
+ permissionMode: 'default',
99
+ permissionProfile: null,
100
+ codexHome: '~/.codex',
101
+ codexCliCommand: 'codex',
102
+ codexCliVersion: null,
103
+ },
104
+ limits: { maxSessionsPerUser: 0, permissionTimeoutSec: 60, codexTimeoutMs: 1_800_000 },
105
+ policy: { unknownCommand: 'confirm', allowExtraCommands: [] },
106
+ autoAllowClaudeTools: ['Read', 'Glob', 'Grep'],
107
+ profiles: {},
108
+ usage: { userAgent: 'claude-code', cacheSec: 180 },
109
+ audit: { channelId: null },
110
+ locale: 'ko',
111
+ logLevel: 'error',
112
+ favorites: [],
113
+ };
114
+ }
115
+ // Wire the chunk-1..3 collaborators against a temp DAB home + a temp workspace,
116
+ // so nothing touches ~/.discord-agent-bridge/. Returns the orchestrator and its
117
+ // deps so a test can inspect state/audit/registry directly.
118
+ function harness(opts = {}) {
119
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-orch-'));
120
+ const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-ws-'));
121
+ const configStore = new ConfigStore(dir);
122
+ configStore.save(makeConfig());
123
+ const stateStore = new StateStore(dir);
124
+ const channelRegistry = new ChannelRegistry(stateStore);
125
+ const configResolver = new ConfigResolver(configStore, channelRegistry);
126
+ const permissionResolver = new PermissionResolver(configStore, configResolver);
127
+ const auditLog = new AuditLog({ baseDir: dir, now: () => '2026-01-01T00:00:00.000Z' });
128
+ const eventBus = new EventBus();
129
+ const modeRegistry = new ModeRegistry();
130
+ const mode = opts.mode ?? new MockMode('claude');
131
+ modeRegistry.register(mode);
132
+ const logger = createLogger('test', { level: 'error' });
133
+ const orchestrator = new SessionOrchestrator({
134
+ channelRegistry,
135
+ modeRegistry,
136
+ eventBus,
137
+ configResolver,
138
+ permissionResolver,
139
+ auditLog,
140
+ logger,
141
+ });
142
+ return { dir, workspace, orchestrator, channelRegistry, stateStore, auditLog, eventBus, mode };
143
+ }
144
+ function readAudit(dir) {
145
+ const file = path.join(dir, 'audit', 'audit.jsonl');
146
+ if (!fs.existsSync(file))
147
+ return [];
148
+ return fs
149
+ .readFileSync(file, 'utf-8')
150
+ .split('\n')
151
+ .filter((l) => l.length > 0)
152
+ .map((l) => JSON.parse(l));
153
+ }
154
+ describe('SessionOrchestrator', () => {
155
+ const cleanup = [];
156
+ afterEach(() => {
157
+ for (const d of cleanup.splice(0))
158
+ fs.rmSync(d, { recursive: true, force: true });
159
+ });
160
+ beforeEach(() => {
161
+ cleanup.length = 0;
162
+ });
163
+ it('start creates a session and persists the binding', async () => {
164
+ const h = harness();
165
+ cleanup.push(h.dir, h.workspace);
166
+ const session = await h.orchestrator.start({
167
+ guildId: 'g1',
168
+ channelId: 'c1',
169
+ mode: 'claude',
170
+ cwd: h.workspace,
171
+ ownerId: 'u1',
172
+ });
173
+ expect(session.sessionId).toBe('sess-c1');
174
+ expect(h.mode.startedCtx).toHaveLength(1);
175
+ expect(h.mode.startedCtx[0]).toMatchObject({ guildId: 'g1', channelId: 'c1', cwd: h.workspace });
176
+ // Binding persisted to the store (source of truth for resume-on-boot).
177
+ const onDisk = new StateStore(h.dir).load();
178
+ expect(onDisk.channels['g1:c1']).toMatchObject({
179
+ mode: 'claude',
180
+ sessionId: 'sess-c1',
181
+ cwd: h.workspace,
182
+ ownerId: 'u1',
183
+ archived: false,
184
+ });
185
+ expect(readAudit(h.dir).some((r) => r.action === 'start')).toBe(true);
186
+ });
187
+ it('threads the resolved tool allowlist onto the started ctx.config (prevents A8)', async () => {
188
+ const h = harness();
189
+ cleanup.push(h.dir, h.workspace);
190
+ await h.orchestrator.start({
191
+ guildId: 'g1',
192
+ channelId: 'c1',
193
+ mode: 'claude',
194
+ cwd: h.workspace,
195
+ ownerId: 'u1',
196
+ });
197
+ // The mode captured the ctx it was started with; the layered allowlist (the
198
+ // config's autoAllowClaudeTools, since no profile narrows it) must be present
199
+ // on config.allowedTools so the mode reads it instead of re-hardcoding one.
200
+ const ctx = h.mode.startedCtx[0];
201
+ expect(ctx.config.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
202
+ expect(ctx.config.autoAllowClaudeTools).toEqual(['Read', 'Glob', 'Grep']);
203
+ });
204
+ it('threads the resolved tool allowlist onto a resumed ctx.config too', async () => {
205
+ const h = harness();
206
+ cleanup.push(h.dir, h.workspace);
207
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c1', mode: 'claude', sessionId: 'sess-c1', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
208
+ await h.orchestrator.resumeAll();
209
+ // resume() built its ctx via the same buildContext path; the resumed mode
210
+ // gets the layered allowlist even though there is no live session override.
211
+ expect(h.mode.resumedCtx).toHaveLength(1);
212
+ expect(h.mode.resumedCtx[0].config.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
213
+ });
214
+ it('processes queued turns sequentially, in order, dropping none (fixes A4)', async () => {
215
+ // Gate the first send so it stays "running" while a second send arrives.
216
+ let release;
217
+ const gate = new Promise((resolve) => {
218
+ release = resolve;
219
+ });
220
+ const mode = new MockMode('claude', { gate });
221
+ const h = harness({ mode });
222
+ cleanup.push(h.dir, h.workspace);
223
+ await h.orchestrator.start({
224
+ guildId: 'g1',
225
+ channelId: 'c1',
226
+ mode: 'claude',
227
+ cwd: h.workspace,
228
+ ownerId: 'u1',
229
+ });
230
+ const first = await h.orchestrator.send('g1', 'c1', { text: 'first' });
231
+ const second = await h.orchestrator.send('g1', 'c1', { text: 'second' });
232
+ // First started; second queued behind it — neither dropped.
233
+ expect(first.status).toBe('started');
234
+ expect(second.status).toBe('queued');
235
+ // While the first is gated, nothing has been delivered to the mode yet.
236
+ expect(mode.lastSession?.turns.map((t) => t.text)).toEqual([]);
237
+ release();
238
+ // Let the drain loop flush both turns.
239
+ await new Promise((r) => setTimeout(r, 0));
240
+ await new Promise((r) => setTimeout(r, 0));
241
+ expect(mode.lastSession?.turns.map((t) => t.text)).toEqual(['first', 'second']);
242
+ });
243
+ it('rejects a turn file that escapes the workspace and emits an error event', async () => {
244
+ const h = harness();
245
+ cleanup.push(h.dir, h.workspace);
246
+ const events = [];
247
+ h.eventBus.on('g1', 'c1', (ev) => events.push(ev));
248
+ await h.orchestrator.start({
249
+ guildId: 'g1',
250
+ channelId: 'c1',
251
+ mode: 'claude',
252
+ cwd: h.workspace,
253
+ ownerId: 'u1',
254
+ });
255
+ // An in-workspace path is accepted (a real file so realpath resolves).
256
+ const insidePath = path.join(h.workspace, 'inside.txt');
257
+ fs.writeFileSync(insidePath, 'x');
258
+ const ok = await h.orchestrator.send('g1', 'c1', {
259
+ text: 'inside',
260
+ files: [{ path: insidePath }],
261
+ });
262
+ expect(ok.status).toBe('started');
263
+ // A traversal path escaping the workspace is rejected before the mode sees it.
264
+ await expect(h.orchestrator.send('g1', 'c1', {
265
+ text: 'escape',
266
+ files: [{ path: path.join(h.workspace, '..', 'etc', 'passwd') }],
267
+ })).rejects.toThrow(/escapes the workspace/);
268
+ await new Promise((r) => setTimeout(r, 0));
269
+ // The escaping turn never reached the mock mode; only the inside turn did.
270
+ expect(h.mode.lastSession?.turns.map((t) => t.text)).toEqual(['inside']);
271
+ // An error event was emitted for the rejected turn.
272
+ expect(events.some((e) => e.kind === 'error' && /escapes the workspace/.test(e.message))).toBe(true);
273
+ // The rejection was audited as denied.
274
+ expect(readAudit(h.dir).some((r) => r.action === 'turn' && r.status === 'denied')).toBe(true);
275
+ });
276
+ it('stop aborts the running session and clears the queue', async () => {
277
+ const h = harness();
278
+ cleanup.push(h.dir, h.workspace);
279
+ await h.orchestrator.start({
280
+ guildId: 'g1',
281
+ channelId: 'c1',
282
+ mode: 'claude',
283
+ cwd: h.workspace,
284
+ ownerId: 'u1',
285
+ });
286
+ const session = h.mode.lastSession;
287
+ await h.orchestrator.stop('g1', 'c1');
288
+ expect(session.stopped).toBe(true);
289
+ // After stop, the channel is no longer active (a send throws).
290
+ await expect(h.orchestrator.send('g1', 'c1', { text: 'late' })).rejects.toThrow(/No active session/);
291
+ // Binding is archived so resume-on-boot skips it.
292
+ expect(h.channelRegistry.get('g1', 'c1')?.archived).toBe(true);
293
+ expect(readAudit(h.dir).some((r) => r.action === 'stop')).toBe(true);
294
+ });
295
+ it('stopAll stops sessions across multiple guilds', async () => {
296
+ const h = harness();
297
+ cleanup.push(h.dir, h.workspace);
298
+ await h.orchestrator.start({ guildId: 'g1', channelId: 'c1', mode: 'claude', cwd: h.workspace, ownerId: 'u1' });
299
+ const s1 = h.mode.lastSession;
300
+ await h.orchestrator.start({ guildId: 'g2', channelId: 'c9', mode: 'claude', cwd: h.workspace, ownerId: 'u2' });
301
+ const s2 = h.mode.lastSession;
302
+ await h.orchestrator.stopAll();
303
+ expect(s1.stopped).toBe(true);
304
+ expect(s2.stopped).toBe(true);
305
+ await expect(h.orchestrator.send('g1', 'c1', { text: 'x' })).rejects.toThrow(/No active session/);
306
+ await expect(h.orchestrator.send('g2', 'c9', { text: 'x' })).rejects.toThrow(/No active session/);
307
+ });
308
+ it('resumeAll rebinds sessions from persisted state via mode.resume', async () => {
309
+ const h = harness();
310
+ cleanup.push(h.dir, h.workspace);
311
+ // Persist two active bindings and one archived one that must be skipped.
312
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c1', mode: 'claude', sessionId: 'sess-c1', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
313
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c2', mode: 'claude', sessionId: 'sess-c2', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
314
+ h.channelRegistry.set({ guildId: 'g2', channelId: 'c3', mode: 'claude', sessionId: 'sess-c3', cwd: h.workspace, ownerId: 'u2', permMode: 'default', profile: null, archived: true });
315
+ await h.orchestrator.resumeAll();
316
+ // Only the two non-archived bindings were resumed.
317
+ expect(h.mode.resumedIds.sort()).toEqual(['sess-c1', 'sess-c2']);
318
+ // A resumed channel is live: a send() is accepted (does not throw "no session").
319
+ await expect(h.orchestrator.send('g1', 'c1', { text: 'after-resume' })).resolves.toMatchObject({ status: 'started' });
320
+ });
321
+ it('resumeAll skips a channel whose resume throws, without failing the others', async () => {
322
+ const mode = new MockMode('claude', { resumeThrows: true });
323
+ const h = harness({ mode });
324
+ cleanup.push(h.dir, h.workspace);
325
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c1', mode: 'claude', sessionId: 'sess-c1', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
326
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c2', mode: 'claude', sessionId: 'sess-c2', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
327
+ // Every resume throws, but resumeAll tolerates it and does not reject.
328
+ await expect(h.orchestrator.resumeAll()).resolves.toBeUndefined();
329
+ // No channel became active because each resume failed and was skipped.
330
+ await expect(h.orchestrator.send('g1', 'c1', { text: 'x' })).rejects.toThrow(/No active session/);
331
+ });
332
+ it('skips resume for a binding with a null sessionId, without failing others', async () => {
333
+ const h = harness();
334
+ cleanup.push(h.dir, h.workspace);
335
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c1', mode: 'claude', sessionId: null, cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
336
+ h.channelRegistry.set({ guildId: 'g1', channelId: 'c2', mode: 'claude', sessionId: 'sess-c2', cwd: h.workspace, ownerId: 'u1', permMode: 'default', profile: null });
337
+ await h.orchestrator.resumeAll();
338
+ // The null-sessionId binding is skipped; the other resumes.
339
+ expect(h.mode.resumedIds).toEqual(['sess-c2']);
340
+ await expect(h.orchestrator.send('g1', 'c1', { text: 'x' })).rejects.toThrow(/No active session/);
341
+ await expect(h.orchestrator.send('g1', 'c2', { text: 'x' })).resolves.toMatchObject({ status: 'started' });
342
+ });
343
+ });
344
+ //# sourceMappingURL=sessionOrchestrator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionOrchestrator.test.js","sourceRoot":"","sources":["../../src/core/sessionOrchestrator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ // AppState v2 zod schemas + inferred types. See docs/DESIGN.md §8 (state.json).
3
+ // Runtime bindings keyed by "<guildId>:<channelId>"; enables resume-on-boot.
4
+ // Unknown fields are tolerated on read and normalized (dropped) on write, since
5
+ // z.object() strips keys not in the schema.
6
+ // Permission modes a SESSION binding may carry (mirrors contracts.ts SessionPermMode):
7
+ // the full Claude PermMode set (SDK's PermissionMode incl. 'dontAsk'/'auto') PLUS the
8
+ // Codex-native sandbox modes a Codex session can be started with from the wizard, so a
9
+ // saved Codex binding validates and resume-on-boot restores it.
10
+ export const permModeSchema = z.enum([
11
+ 'default',
12
+ 'acceptEdits',
13
+ 'bypassPermissions',
14
+ 'plan',
15
+ 'dontAsk',
16
+ 'auto',
17
+ 'read-only',
18
+ 'workspace-write',
19
+ 'danger-full-access',
20
+ ]);
21
+ export const STATE_VERSION = 2;
22
+ export const channelBindingSchema = z.object({
23
+ guildId: z.string(),
24
+ mode: z.enum(['claude', 'codex']),
25
+ sessionId: z.string().nullable(),
26
+ cwd: z.string(),
27
+ ownerId: z.string(),
28
+ permissionMode: permModeSchema,
29
+ permissionProfile: z.string().nullable(),
30
+ projectAuth: z
31
+ .object({
32
+ allowedRoleIds: z.array(z.string()),
33
+ allowedUserIds: z.array(z.string()),
34
+ })
35
+ .optional(),
36
+ createdAt: z.string(),
37
+ updatedAt: z.string(),
38
+ archived: z.boolean(),
39
+ });
40
+ export const appStateSchema = z.object({
41
+ version: z.number(),
42
+ channels: z.record(z.string(), channelBindingSchema),
43
+ scheduledCommands: z.array(z.unknown()).default([]),
44
+ });
45
+ // A fresh, empty v2 state used when no state.json exists yet.
46
+ export function emptyState() {
47
+ return { version: STATE_VERSION, channels: {}, scheduledCommands: [] };
48
+ }
49
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/core/state/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,4CAA4C;AAE5C,uFAAuF;AACvF,sFAAsF;AACtF,uFAAuF;AACvF,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC;IACnC,SAAS;IACT,aAAa;IACb,mBAAmB;IACnB,MAAM;IACN,SAAS;IACT,MAAM;IACN,WAAW;IACX,iBAAiB;IACjB,oBAAoB;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAE/B,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,cAAc,EAAE,cAAc;IAC9B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,WAAW,EAAE,CAAC;SACX,MAAM,CAAC;QACN,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACnC,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KACpC,CAAC;SACD,QAAQ,EAAE;IACb,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CACtB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC;IACpD,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACpD,CAAC,CAAC;AAKH,8DAA8D;AAC9D,MAAM,UAAU,UAAU;IACxB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC;AACzE,CAAC"}
@@ -0,0 +1,86 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { appStateSchema, emptyState, STATE_VERSION } from './schema.js';
5
+ // Versioned JSON state store. Loads state.json, runs ordered migrations up to the
6
+ // current version, zod-validates, and writes atomically (tmp + rename). Unknown
7
+ // fields are tolerated on read (z.object strips them → normalized on write).
8
+ // See docs/DESIGN.md §8. The base dir is injectable so tests never touch the real
9
+ // home directory: explicit ctor arg > env DAB_HOME > ~/.discord-agent-bridge/.
10
+ const DEFAULT_DIR_NAME = '.discord-agent-bridge';
11
+ function defaultBaseDir() {
12
+ const fromEnv = process.env.DAB_HOME;
13
+ if (fromEnv && fromEnv.length > 0)
14
+ return fromEnv;
15
+ return path.join(os.homedir(), DEFAULT_DIR_NAME);
16
+ }
17
+ // Ordered migrations, keyed by the version they upgrade FROM. Each returns the
18
+ // object shape at the next version. Run in sequence until STATE_VERSION.
19
+ // v1 stored channels keyed by bare "<channelId>" without a guildId field; v2
20
+ // rekeys them to "<guildId>:<channelId>" (§8, store.ts note). v1 is hypothetical
21
+ // (pre-release) but the migration path is exercised for forward safety.
22
+ const migrations = {
23
+ 1: (raw) => {
24
+ const oldChannels = raw['channels'] ?? {};
25
+ const channels = {};
26
+ for (const [key, binding] of Object.entries(oldChannels)) {
27
+ const guildId = typeof binding['guildId'] === 'string' ? binding['guildId'] : 'unknown';
28
+ const rekeyed = key.includes(':') ? key : `${guildId}:${key}`;
29
+ channels[rekeyed] = binding;
30
+ }
31
+ return { ...raw, version: 2, channels };
32
+ },
33
+ };
34
+ function migrate(raw) {
35
+ let current = raw;
36
+ let version = typeof current['version'] === 'number' ? current['version'] : STATE_VERSION;
37
+ while (version < STATE_VERSION) {
38
+ const step = migrations[version];
39
+ if (!step) {
40
+ throw new Error(`No migration registered from state version ${version}.`);
41
+ }
42
+ current = step(current);
43
+ const next = typeof current['version'] === 'number' ? current['version'] : version + 1;
44
+ if (next <= version) {
45
+ throw new Error(`Migration from state version ${version} did not advance the version.`);
46
+ }
47
+ version = next;
48
+ }
49
+ return current;
50
+ }
51
+ export class StateStore {
52
+ baseDir;
53
+ constructor(baseDir) {
54
+ this.baseDir = baseDir ?? defaultBaseDir();
55
+ }
56
+ get dir() {
57
+ return this.baseDir;
58
+ }
59
+ get statePath() {
60
+ return path.join(this.baseDir, 'state.json');
61
+ }
62
+ // Load state.json → migrate → zod-validate. When absent, return fresh v2 state.
63
+ load() {
64
+ if (!fs.existsSync(this.statePath)) {
65
+ return emptyState();
66
+ }
67
+ const raw = JSON.parse(fs.readFileSync(this.statePath, 'utf-8'));
68
+ if (typeof raw !== 'object' || raw === null) {
69
+ throw new Error(`Invalid state file at ${this.statePath}: expected a JSON object.`);
70
+ }
71
+ const migrated = migrate(raw);
72
+ return appStateSchema.parse(migrated);
73
+ }
74
+ // Validate, normalize to current version, then write atomically (tmp + rename).
75
+ save(state) {
76
+ const validated = appStateSchema.parse({ ...state, version: STATE_VERSION });
77
+ if (!fs.existsSync(this.baseDir)) {
78
+ fs.mkdirSync(this.baseDir, { recursive: true });
79
+ }
80
+ const data = JSON.stringify(validated, null, 2) + '\n';
81
+ const tmp = `${this.statePath}.tmp`;
82
+ fs.writeFileSync(tmp, data, { encoding: 'utf-8' });
83
+ fs.renameSync(tmp, this.statePath);
84
+ }
85
+ }
86
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/state/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { StateStore } from './store.js';
6
+ import { STATE_VERSION } from './schema.js';
7
+ function binding(overrides = {}) {
8
+ return {
9
+ guildId: 'g1',
10
+ mode: 'claude',
11
+ sessionId: 'sess-1',
12
+ cwd: '/abs/workspace',
13
+ ownerId: 'u1',
14
+ permissionMode: 'default',
15
+ permissionProfile: null,
16
+ createdAt: '2026-01-01T00:00:00.000Z',
17
+ updatedAt: '2026-01-01T00:00:00.000Z',
18
+ archived: false,
19
+ ...overrides,
20
+ };
21
+ }
22
+ describe('StateStore', () => {
23
+ let dir;
24
+ beforeEach(() => {
25
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-state-'));
26
+ });
27
+ afterEach(() => {
28
+ fs.rmSync(dir, { recursive: true, force: true });
29
+ });
30
+ it('returns empty v2 state when file is absent', () => {
31
+ const store = new StateStore(dir);
32
+ const state = store.load();
33
+ expect(state.version).toBe(STATE_VERSION);
34
+ expect(state.channels).toEqual({});
35
+ expect(state.scheduledCommands).toEqual([]);
36
+ });
37
+ it('round-trips save → load', () => {
38
+ const store = new StateStore(dir);
39
+ const state = {
40
+ version: STATE_VERSION,
41
+ channels: { 'g1:c1': binding() },
42
+ scheduledCommands: [],
43
+ };
44
+ store.save(state);
45
+ expect(store.load()).toEqual(state);
46
+ });
47
+ it('tolerates and normalizes unknown fields on read', () => {
48
+ const store = new StateStore(dir);
49
+ fs.writeFileSync(store.statePath, JSON.stringify({
50
+ version: STATE_VERSION,
51
+ channels: { 'g1:c1': { ...binding(), bogusField: 'x' } },
52
+ scheduledCommands: [],
53
+ extraTopLevel: true,
54
+ }), 'utf-8');
55
+ const loaded = store.load();
56
+ expect(loaded.channels['g1:c1']).toEqual(binding());
57
+ expect('extraTopLevel' in loaded).toBe(false);
58
+ expect('bogusField' in loaded.channels['g1:c1']).toBe(false);
59
+ });
60
+ it('runs the v1 → v2 migration and rekeys channels', () => {
61
+ const store = new StateStore(dir);
62
+ // v1: channels keyed by bare channelId, guildId inside the binding.
63
+ fs.writeFileSync(store.statePath, JSON.stringify({
64
+ version: 1,
65
+ channels: { c1: binding({ guildId: 'g9' }) },
66
+ }), 'utf-8');
67
+ const loaded = store.load();
68
+ expect(loaded.version).toBe(STATE_VERSION);
69
+ expect(loaded.channels['g9:c1']).toBeDefined();
70
+ expect(loaded.channels['c1']).toBeUndefined();
71
+ });
72
+ it('rejects malformed state (bad permissionMode)', () => {
73
+ const store = new StateStore(dir);
74
+ fs.writeFileSync(store.statePath, JSON.stringify({
75
+ version: STATE_VERSION,
76
+ channels: { 'g1:c1': { ...binding(), permissionMode: 'nonsense' } },
77
+ }), 'utf-8');
78
+ expect(() => store.load()).toThrow();
79
+ });
80
+ it('never overwrites a valid target when the rename step fails', () => {
81
+ const store = new StateStore(dir);
82
+ // Occupy the state.json path with a non-empty DIRECTORY so tmp write succeeds
83
+ // but rename onto it fails naturally (EISDIR/ENOTEMPTY) — no ESM spy needed.
84
+ fs.mkdirSync(store.statePath, { recursive: true });
85
+ fs.writeFileSync(path.join(store.statePath, 'sentinel'), 'keep', 'utf-8');
86
+ const state = {
87
+ version: STATE_VERSION,
88
+ channels: { 'g1:c1': binding() },
89
+ scheduledCommands: [],
90
+ };
91
+ expect(() => store.save(state)).toThrow();
92
+ // The occupied path is untouched: still a directory holding the sentinel,
93
+ // never replaced by a partial file. And the tmp scratch file is the only
94
+ // extra artifact — it never became the live state.json.
95
+ expect(fs.statSync(store.statePath).isDirectory()).toBe(true);
96
+ expect(fs.readFileSync(path.join(store.statePath, 'sentinel'), 'utf-8')).toBe('keep');
97
+ });
98
+ });
99
+ //# sourceMappingURL=store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.js","sourceRoot":"","sources":["../../../src/core/state/store.test.ts"],"names":[],"mappings":"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"}