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,116 @@
1
+ const LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
2
+ const PLACEHOLDER = '[REDACTED]';
3
+ // Sensitive object keys whose values are replaced wholesale, regardless of shape.
4
+ const SENSITIVE_KEYS = new Set([
5
+ 'token',
6
+ 'authorization',
7
+ 'accesstoken',
8
+ 'refreshtoken',
9
+ 'apikey',
10
+ 'secret',
11
+ 'password',
12
+ 'credentials',
13
+ ]);
14
+ // Value-shape patterns for secrets that may appear inside free-form strings.
15
+ const VALUE_PATTERNS = [
16
+ // Discord bot token: <base64 userId>.<base64 timestamp>.<base64 hmac>. The real
17
+ // shape is tight (~23-28 . ~6-7 . ~27-40 chars) with word boundaries, so benign
18
+ // dotted strings (e.g. file hashes like "deadbeef.sig.0123456789") are not
19
+ // clobbered.
20
+ /\b[A-Za-z0-9_-]{23,28}\.[A-Za-z0-9_-]{6,7}\.[A-Za-z0-9_-]{27,40}\b/g,
21
+ // Anthropic / OAuth style bearer tokens (sk-..., sk-ant-..., long opaque tokens).
22
+ /\bsk-[A-Za-z0-9-]{16,}\b/g,
23
+ // "Authorization: Bearer <token>" / "Bearer <token>" (allow multiple spaces).
24
+ /(?<=Bearer\s{1,4})[A-Za-z0-9._-]{8,}/gi,
25
+ ];
26
+ function redactString(input) {
27
+ let out = input;
28
+ for (const pattern of VALUE_PATTERNS) {
29
+ out = out.replace(pattern, PLACEHOLDER);
30
+ }
31
+ return out;
32
+ }
33
+ // Recursively redact a value: sensitive keys → placeholder, strings → pattern-scrubbed.
34
+ // Cycles are broken via a seen-set. Depth is bounded to avoid pathological payloads.
35
+ export function redact(value, seen = new WeakSet(), depth = 0) {
36
+ if (typeof value === 'string')
37
+ return redactString(value);
38
+ if (value === null || typeof value !== 'object')
39
+ return value;
40
+ if (depth > 6)
41
+ return '[…]';
42
+ if (seen.has(value))
43
+ return '[Circular]';
44
+ seen.add(value);
45
+ if (Array.isArray(value)) {
46
+ return value.map((item) => redact(item, seen, depth + 1));
47
+ }
48
+ const out = {};
49
+ for (const [key, val] of Object.entries(value)) {
50
+ if (SENSITIVE_KEYS.has(key.toLowerCase())) {
51
+ out[key] = PLACEHOLDER;
52
+ }
53
+ else {
54
+ out[key] = redact(val, seen, depth + 1);
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+ const consoleSink = {
60
+ write(level, line) {
61
+ if (level === 'error')
62
+ console.error(line);
63
+ else if (level === 'warn')
64
+ console.warn(line);
65
+ else
66
+ console.log(line);
67
+ },
68
+ };
69
+ function formatMeta(meta) {
70
+ if (meta.length === 0)
71
+ return '';
72
+ const parts = meta.map((m) => {
73
+ const scrubbed = redact(m);
74
+ if (typeof scrubbed === 'string')
75
+ return scrubbed;
76
+ try {
77
+ return JSON.stringify(scrubbed);
78
+ }
79
+ catch {
80
+ return String(scrubbed);
81
+ }
82
+ });
83
+ return ' ' + parts.join(' ');
84
+ }
85
+ class RedactingLogger {
86
+ name;
87
+ threshold;
88
+ sink;
89
+ constructor(name, options) {
90
+ this.name = name;
91
+ this.threshold = LEVEL_ORDER[options.level ?? 'info'];
92
+ this.sink = options.sink ?? consoleSink;
93
+ }
94
+ log(level, message, meta) {
95
+ if (LEVEL_ORDER[level] < this.threshold)
96
+ return;
97
+ const line = `[${level.toUpperCase()}] ${this.name}: ${redactString(message)}${formatMeta(meta)}`;
98
+ this.sink.write(level, line);
99
+ }
100
+ debug(message, ...meta) {
101
+ this.log('debug', message, meta);
102
+ }
103
+ info(message, ...meta) {
104
+ this.log('info', message, meta);
105
+ }
106
+ warn(message, ...meta) {
107
+ this.log('warn', message, meta);
108
+ }
109
+ error(message, ...meta) {
110
+ this.log('error', message, meta);
111
+ }
112
+ }
113
+ export function createLogger(name, options = {}) {
114
+ return new RedactingLogger(name, options);
115
+ }
116
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createLogger, redact } from './logger.js';
3
+ function captureSink() {
4
+ const lines = [];
5
+ return {
6
+ lines,
7
+ sink: {
8
+ write(level, line) {
9
+ lines.push({ level, line });
10
+ },
11
+ },
12
+ };
13
+ }
14
+ describe('redact()', () => {
15
+ it('redacts sensitive object keys', () => {
16
+ const out = redact({ token: 'abc.def.ghi', clientId: 'safe', authorization: 'Bearer xyz' });
17
+ expect(out.token).toBe('[REDACTED]');
18
+ expect(out.authorization).toBe('[REDACTED]');
19
+ expect(out.clientId).toBe('safe');
20
+ });
21
+ it('scrubs Discord-token-shaped strings in free text', () => {
22
+ // Synthetic, zero-entropy value matching the Discord-token SHAPE
23
+ // (23-28 . 6-7 . 27-40 chars). Assembled at runtime so no realistic
24
+ // token literal lives in source (avoids secret scanners).
25
+ const fake = ['X'.repeat(24), 'Y'.repeat(6), 'Z'.repeat(28)].join('.');
26
+ const out = redact(`connecting with ${fake} now`);
27
+ expect(out).not.toContain(fake);
28
+ expect(out).toContain('[REDACTED]');
29
+ });
30
+ it('scrubs Bearer tokens and sk- keys', () => {
31
+ expect(redact('Authorization: Bearer abcdef123456')).not.toContain('abcdef123456');
32
+ // Synthetic sk--prefixed key (>=16 tail chars) matching the pattern shape.
33
+ const skKey = 'sk-' + 'k'.repeat(20);
34
+ expect(redact(`key ${skKey} here`)).not.toContain(skKey);
35
+ });
36
+ it('does NOT over-redact a benign dotted string that only resembles a token', () => {
37
+ // Short segments (8 . 3 . 10) fall well below the Discord-token shape
38
+ // (23-28 . 6-7 . 27-40), so a file-hash-like value must survive intact.
39
+ const benign = 'deadbeef.sig.0123456789';
40
+ expect(redact(`artifact ${benign} built`)).toBe(`artifact ${benign} built`);
41
+ });
42
+ it('scrubs a Bearer token separated by multiple spaces', () => {
43
+ // The lookbehind allows 1-4 spaces after "Bearer" so a hand-spaced header
44
+ // is still redacted. Assembled at runtime; not a realistic secret.
45
+ const tok = 'sometoken12345';
46
+ const out = redact(`Bearer ${tok}`);
47
+ expect(out).not.toContain(tok);
48
+ expect(out).toContain('[REDACTED]');
49
+ });
50
+ it('handles nested structures and circular references', () => {
51
+ const obj = { a: { token: 'x.y.z' } };
52
+ obj.self = obj;
53
+ const out = redact(obj);
54
+ expect(out.a.token).toBe('[REDACTED]');
55
+ expect(out.self).toBe('[Circular]');
56
+ });
57
+ });
58
+ describe('createLogger()', () => {
59
+ it('redacts a token embedded in a logged payload', () => {
60
+ const { lines, sink } = captureSink();
61
+ const logger = createLogger('test', { level: 'info', sink });
62
+ // Synthetic sentinel under the sensitive 'token' key (redacted by key).
63
+ const secret = 'S'.repeat(40);
64
+ logger.info('bot login', { discord: { token: secret } });
65
+ const joined = lines.map((l) => l.line).join('\n');
66
+ expect(joined).not.toContain(secret);
67
+ expect(joined).toContain('[REDACTED]');
68
+ });
69
+ it('gates messages below the configured level', () => {
70
+ const { lines, sink } = captureSink();
71
+ const logger = createLogger('test', { level: 'warn', sink });
72
+ logger.debug('debug msg');
73
+ logger.info('info msg');
74
+ logger.warn('warn msg');
75
+ logger.error('error msg');
76
+ const levels = lines.map((l) => l.level);
77
+ expect(levels).toEqual(['warn', 'error']);
78
+ });
79
+ it('defaults to info level', () => {
80
+ const { lines, sink } = captureSink();
81
+ const logger = createLogger('test', { sink });
82
+ logger.debug('nope');
83
+ logger.info('yep');
84
+ expect(lines).toHaveLength(1);
85
+ expect(lines[0].level).toBe('info');
86
+ });
87
+ });
88
+ //# sourceMappingURL=logger.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../../src/core/logger.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ // name → AgentMode; the single place modes are registered and looked up (§4, §10).
2
+ // This is the extensibility seam: adding a backend is register(mode) + nothing
3
+ // else in core changes. Modes self-identify via their readonly `name`, so the
4
+ // registry keys on mode.name rather than a separately-passed string.
5
+ export class ModeRegistry {
6
+ modes = new Map();
7
+ // Register a mode instance. Throws on a duplicate name so a mis-wired double
8
+ // registration surfaces at boot rather than silently shadowing.
9
+ register(mode) {
10
+ if (this.modes.has(mode.name)) {
11
+ throw new Error(`Mode '${mode.name}' is already registered.`);
12
+ }
13
+ this.modes.set(mode.name, mode);
14
+ }
15
+ // Look up a registered mode by name; throws if unknown so callers get a clear
16
+ // error instead of an undefined dereference.
17
+ get(name) {
18
+ const mode = this.modes.get(name);
19
+ if (!mode) {
20
+ throw new Error(`Unknown mode '${name}'. Registered: ${this.list().join(', ') || '(none)'}.`);
21
+ }
22
+ return mode;
23
+ }
24
+ // Non-throwing lookup for callers that want to test presence.
25
+ has(name) {
26
+ return this.modes.has(name);
27
+ }
28
+ // Names of all registered modes, in registration order.
29
+ list() {
30
+ return [...this.modes.keys()];
31
+ }
32
+ }
33
+ //# sourceMappingURL=modeRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modeRegistry.js","sourceRoot":"","sources":["../../src/core/modeRegistry.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,+EAA+E;AAC/E,8EAA8E;AAC9E,qEAAqE;AACrE,MAAM,OAAO,YAAY;IACN,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEtD,6EAA6E;IAC7E,gEAAgE;IAChE,QAAQ,CAAC,IAAe;QACtB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,IAAI,0BAA0B,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,8EAA8E;IAC9E,6CAA6C;IAC7C,GAAG,CAAC,IAAY;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,kBAAkB,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8DAA8D;IAC9D,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,wDAAwD;IACxD,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAChC,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ export class PermissionResolver {
2
+ configStore;
3
+ configResolver;
4
+ constructor(configStore, configResolver) {
5
+ this.configStore = configStore;
6
+ this.configResolver = configResolver;
7
+ }
8
+ resolve(guildId, channelId, override) {
9
+ const config = this.configStore.load();
10
+ const layered = this.configResolver.resolve(guildId, channelId);
11
+ // Profile: session override wins, else layered value. `null` explicitly
12
+ // clears the profile; `undefined` falls through.
13
+ const profileName = override && 'profile' in override ? (override.profile ?? null) : layered.permissionProfile;
14
+ // Base permission mode from the layered defaults; the global auto-allow set
15
+ // is the default tool allowlist when no profile narrows it. Widened to
16
+ // SessionPermMode so a per-session override may carry a Codex sandbox mode.
17
+ let permMode = layered.permissionMode;
18
+ let allowedTools = [...config.autoAllowClaudeTools];
19
+ let policyTier;
20
+ // A named profile bundles mode + tools + tier (§7A). Applied over the base.
21
+ if (profileName) {
22
+ const profile = config.profiles[profileName];
23
+ if (!profile) {
24
+ throw new Error(`Unknown permission profile '${profileName}'.`);
25
+ }
26
+ permMode = profile.permissionMode;
27
+ allowedTools = [...profile.allowedTools];
28
+ policyTier = profile.policyTier;
29
+ }
30
+ // A directly-set per-session permission mode wins over everything, including
31
+ // a profile's bundled mode (the operator explicitly overrode it via /mode).
32
+ if (override?.permMode !== undefined) {
33
+ permMode = override.permMode;
34
+ }
35
+ return {
36
+ permMode,
37
+ profile: profileName,
38
+ allowedTools,
39
+ ...(policyTier ? { policyTier } : {}),
40
+ };
41
+ }
42
+ }
43
+ //# sourceMappingURL=permissionResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionResolver.js","sourceRoot":"","sources":["../../src/core/permissionResolver.ts"],"names":[],"mappings":"AAmCA,MAAM,OAAO,kBAAkB;IAEV;IACA;IAFnB,YACmB,WAAwB,EACxB,cAA8B;QAD9B,gBAAW,GAAX,WAAW,CAAa;QACxB,mBAAc,GAAd,cAAc,CAAgB;IAC9C,CAAC;IAEJ,OAAO,CACL,OAAe,EACf,SAAiB,EACjB,QAA0B;QAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEhE,wEAAwE;QACxE,iDAAiD;QACjD,MAAM,WAAW,GACf,QAAQ,IAAI,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC;QAE7F,4EAA4E;QAC5E,uEAAuE;QACvE,4EAA4E;QAC5E,IAAI,QAAQ,GAAoB,OAAO,CAAC,cAAc,CAAC;QACvD,IAAI,YAAY,GAAa,CAAC,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC9D,IAAI,UAAyC,CAAC;QAE9C,4EAA4E;QAC5E,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,+BAA+B,WAAW,IAAI,CAAC,CAAC;YAClE,CAAC;YACD,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;YAClC,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;YACzC,UAAU,GAAG,OAAO,CAAC,UAA+B,CAAC;QACvD,CAAC;QAED,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,QAAQ,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;YACrC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAC/B,CAAC;QAED,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,WAAW;YACpB,YAAY;YACZ,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,136 @@
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
+ function makeConfig(overrides = {}) {
12
+ return {
13
+ version: CONFIG_VERSION,
14
+ discord: { token: 'bot-token-abc', clientId: '123456789' },
15
+ auth: { adminRoleIds: [], executeRoleIds: [], readOnlyRoleIds: [], dmPolicy: 'deny' },
16
+ defaults: {
17
+ mode: 'claude',
18
+ claudeModel: 'opus',
19
+ codexModel: '',
20
+ permissionMode: 'default',
21
+ permissionProfile: null,
22
+ codexHome: '~/.codex',
23
+ codexCliCommand: 'codex',
24
+ codexCliVersion: null,
25
+ },
26
+ limits: { maxSessionsPerUser: 0, permissionTimeoutSec: 60, codexTimeoutMs: 1_800_000 },
27
+ policy: { unknownCommand: 'confirm', allowExtraCommands: [] },
28
+ autoAllowClaudeTools: ['Read', 'Glob', 'Grep'],
29
+ profiles: {
30
+ readonly: { permissionMode: 'plan', allowedTools: ['Read', 'Glob', 'Grep'], policyTier: 'read-only' },
31
+ edit: { permissionMode: 'acceptEdits', allowedTools: ['Read', 'Edit', 'Write', 'Bash'], policyTier: 'normal' },
32
+ },
33
+ usage: { userAgent: 'claude-code', cacheSec: 180 },
34
+ audit: { channelId: null },
35
+ locale: 'ko',
36
+ logLevel: 'info',
37
+ favorites: [],
38
+ ...overrides,
39
+ };
40
+ }
41
+ function binding(overrides = {}) {
42
+ return {
43
+ guildId: 'g1',
44
+ channelId: 'c1',
45
+ mode: 'claude',
46
+ sessionId: null,
47
+ cwd: '/abs/workspace',
48
+ ownerId: 'u1',
49
+ permMode: 'default',
50
+ profile: null,
51
+ ...overrides,
52
+ };
53
+ }
54
+ describe('PermissionResolver', () => {
55
+ let dir;
56
+ let store;
57
+ function build() {
58
+ const registry = new ChannelRegistry(new StateStore(dir), () => '2026-01-01T00:00:00.000Z');
59
+ const configResolver = new ConfigResolver(store, registry);
60
+ const resolver = new PermissionResolver(store, configResolver);
61
+ return { resolver, registry };
62
+ }
63
+ beforeEach(() => {
64
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-permres-'));
65
+ store = new ConfigStore(dir);
66
+ });
67
+ afterEach(() => {
68
+ fs.rmSync(dir, { recursive: true, force: true });
69
+ });
70
+ it('default: global permissionMode + global auto-allow tools when nothing overrides', () => {
71
+ store.save(makeConfig());
72
+ const { resolver } = build();
73
+ const r = resolver.resolve('g1', 'c1');
74
+ expect(r.permMode).toBe('default');
75
+ expect(r.profile).toBeNull();
76
+ expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
77
+ expect(r.policyTier).toBeUndefined();
78
+ });
79
+ it('layers permission mode global→server→project (project wins)', () => {
80
+ store.save(makeConfig());
81
+ store.saveServerConfig({ version: 1, guildId: 'g1', defaults: { permissionMode: 'acceptEdits' } });
82
+ const { resolver, registry } = build();
83
+ // Server-only → acceptEdits.
84
+ expect(resolver.resolve('g1', 'c9').permMode).toBe('acceptEdits');
85
+ // Project binding wins.
86
+ registry.set(binding({ permMode: 'plan' }));
87
+ expect(resolver.resolve('g1', 'c1').permMode).toBe('plan');
88
+ });
89
+ it('a resolved profile bundles its mode, tools, and tier', () => {
90
+ store.save(makeConfig());
91
+ const { resolver, registry } = build();
92
+ registry.set(binding({ profile: 'readonly' }));
93
+ const r = resolver.resolve('g1', 'c1');
94
+ expect(r.profile).toBe('readonly');
95
+ expect(r.permMode).toBe('plan'); // from profile, over binding permMode 'default'
96
+ expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
97
+ expect(r.policyTier).toBe('read-only');
98
+ });
99
+ it('session override permMode wins over layered value and profile', () => {
100
+ store.save(makeConfig());
101
+ const { resolver, registry } = build();
102
+ registry.set(binding({ profile: 'readonly' })); // profile would give 'plan'
103
+ const r = resolver.resolve('g1', 'c1', { permMode: 'bypassPermissions' });
104
+ expect(r.permMode).toBe('bypassPermissions');
105
+ // Profile still supplies the tool allowlist/tier unless the profile itself was overridden.
106
+ expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
107
+ expect(r.profile).toBe('readonly');
108
+ });
109
+ it('session override can switch the profile, changing bundled tools', () => {
110
+ store.save(makeConfig());
111
+ const { resolver, registry } = build();
112
+ registry.set(binding({ profile: 'readonly' }));
113
+ const r = resolver.resolve('g1', 'c1', { profile: 'edit' });
114
+ expect(r.profile).toBe('edit');
115
+ expect(r.permMode).toBe('acceptEdits');
116
+ expect(r.allowedTools).toEqual(['Read', 'Edit', 'Write', 'Bash']);
117
+ expect(r.policyTier).toBe('normal');
118
+ });
119
+ it('session override can clear the profile back to layered/global tools', () => {
120
+ store.save(makeConfig());
121
+ const { resolver, registry } = build();
122
+ registry.set(binding({ profile: 'readonly', permMode: 'acceptEdits' }));
123
+ const r = resolver.resolve('g1', 'c1', { profile: null });
124
+ expect(r.profile).toBeNull();
125
+ // No profile → layered permMode (binding's acceptEdits) + global auto-allow tools.
126
+ expect(r.permMode).toBe('acceptEdits');
127
+ expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
128
+ });
129
+ it('throws on an unknown profile name', () => {
130
+ store.save(makeConfig());
131
+ const { resolver, registry } = build();
132
+ registry.set(binding({ profile: 'nope' }));
133
+ expect(() => resolver.resolve('g1', 'c1')).toThrow(/unknown permission profile/i);
134
+ });
135
+ });
136
+ //# sourceMappingURL=permissionResolver.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionResolver.test.js","sourceRoot":"","sources":["../../src/core/permissionResolver.test.ts"],"names":[],"mappings":"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"}