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,305 @@
1
+ import { t } from '../i18n.js';
2
+ export class ChannelWizard {
3
+ opts;
4
+ browser;
5
+ step = 'folder';
6
+ selection;
7
+ // Pending (not-yet-confirmed) select values for the current step. A select onChange
8
+ // writes here + re-renders; the step's confirm button reads it (falling back to the
9
+ // committed selection when the dropdown was never touched) and advances.
10
+ pending = {};
11
+ // The Discord user who opened this wizard (the driver). Only they advance it, so a
12
+ // bystander's stray select/button cannot corrupt another driver's flow (§7.1).
13
+ ownerId;
14
+ // The channel id the confirmed session was bound to (the freshly created session
15
+ // channel, A4D-style). Null until start succeeds; the router reads it to link the
16
+ // new channel back to the driver.
17
+ createdChannelId = null;
18
+ constructor(options) {
19
+ this.opts = options;
20
+ this.browser = options.browser;
21
+ this.ownerId = options.ownerId;
22
+ this.selection = {
23
+ cwd: null,
24
+ backend: options.defaults.backend,
25
+ model: options.defaults.model,
26
+ effort: options.defaultEffortFor(options.defaults.backend),
27
+ permMode: options.defaults.permMode,
28
+ profile: options.defaults.profile,
29
+ };
30
+ }
31
+ currentStep() {
32
+ return this.step;
33
+ }
34
+ // The folder currently in view in the wizard's browser. Used by the router's
35
+ // 📁 Create flow to mkdir a subfolder in the CURRENT browsed directory, and by the
36
+ // Resume Session flow to scope listResumable to it. Read-only.
37
+ browserCwd() {
38
+ return this.browser.cwd();
39
+ }
40
+ // The collected selection so far (read-only view for tests / status).
41
+ current() {
42
+ return { ...this.selection };
43
+ }
44
+ // Advance the state machine by one input. Returns the new step. Unknown inputs for
45
+ // the current step are ignored (the step is unchanged) so a stray interaction does
46
+ // not corrupt the flow. The perm step's start button calls orchestrator.start.
47
+ async handle(input) {
48
+ if (input.id === 'cancel') {
49
+ this.step = 'cancelled';
50
+ return this.step;
51
+ }
52
+ switch (this.step) {
53
+ case 'folder':
54
+ await this.handleFolder(input);
55
+ break;
56
+ case 'backend':
57
+ this.handleBackend(input);
58
+ break;
59
+ case 'model':
60
+ this.handleModel(input);
61
+ break;
62
+ case 'effort':
63
+ this.handleEffort(input);
64
+ break;
65
+ case 'perm':
66
+ await this.handlePerm(input);
67
+ break;
68
+ default:
69
+ break; // done / cancelled: no further transitions
70
+ }
71
+ return this.step;
72
+ }
73
+ async handleFolder(input) {
74
+ if (input.id === 'dir:into' && input.value) {
75
+ this.browser.into(input.value);
76
+ }
77
+ else if (input.id === 'dir:up') {
78
+ this.browser.up();
79
+ }
80
+ else if (input.id === 'dir:here') {
81
+ this.selection.cwd = this.browser.select();
82
+ this.step = 'backend';
83
+ }
84
+ }
85
+ // Backend step: the select stores a pending backend + re-renders; 'backend.next'
86
+ // commits it (or the current default) and advances to the model step. Committing a
87
+ // NEW backend resets model/effort/perm to that backend's defaults so the later steps
88
+ // never show another backend's catalog.
89
+ handleBackend(input) {
90
+ if (input.id === 'backend' && input.value) {
91
+ this.pending.backend = input.value;
92
+ }
93
+ else if (input.id === 'backend.next') {
94
+ const chosen = this.pending.backend ?? this.selection.backend;
95
+ if (chosen !== this.selection.backend)
96
+ this.applyBackend(chosen);
97
+ this.pending.backend = undefined;
98
+ this.step = 'model';
99
+ }
100
+ }
101
+ // Model step: the select stores a pending model; 'model.next' commits it (or the
102
+ // current default) and advances to the reasoning-effort step.
103
+ handleModel(input) {
104
+ if (input.id === 'model' && input.value) {
105
+ this.pending.model = input.value;
106
+ }
107
+ else if (input.id === 'model.next') {
108
+ this.selection.model = this.pending.model ?? this.selection.model;
109
+ this.pending.model = undefined;
110
+ this.step = 'effort';
111
+ }
112
+ }
113
+ // Reasoning-effort step: the select stores a pending effort; 'effort.next' commits it
114
+ // (or the current default) and advances to the permission step.
115
+ handleEffort(input) {
116
+ if (input.id === 'effort' && input.value) {
117
+ this.pending.effort = input.value;
118
+ }
119
+ else if (input.id === 'effort.next') {
120
+ this.selection.effort = this.pending.effort ?? this.selection.effort;
121
+ this.pending.effort = undefined;
122
+ this.step = 'perm';
123
+ }
124
+ }
125
+ // Permission step (final): a profile pick (Claude quick path) OR a raw mode / Codex
126
+ // sandbox pick, plus the '✅ 시작' button. The selects store a pending value; the
127
+ // start button commits it and runs orchestrator.start. A profile pick uses the
128
+ // 'perm.profile' channel; a mode/sandbox pick uses 'perm.mode'.
129
+ async handlePerm(input) {
130
+ if (input.id === 'perm.profile' && input.value) {
131
+ this.pending.perm = `profile:${input.value}`;
132
+ }
133
+ else if (input.id === 'perm.mode' && input.value) {
134
+ this.pending.perm = `mode:${input.value}`;
135
+ }
136
+ else if (input.id === 'perm.start') {
137
+ this.commitPending();
138
+ await this.start();
139
+ }
140
+ }
141
+ // Commit the pending permission pick (if any) onto the selection. Falls back to the
142
+ // committed default when the dropdown was never touched, so pressing 시작 without
143
+ // changing anything still starts with the resolved defaults.
144
+ commitPending() {
145
+ const pending = this.pending.perm;
146
+ if (!pending)
147
+ return;
148
+ if (pending.startsWith('profile:')) {
149
+ const value = pending.slice('profile:'.length);
150
+ this.selection.profile = value === '__raw__' ? null : value;
151
+ }
152
+ else if (pending.startsWith('mode:')) {
153
+ // A raw mode (Claude PermMode) or a Codex sandbox value; either way it clears any
154
+ // profile and rides the permMode channel to the runner.
155
+ this.selection.permMode = pending.slice('mode:'.length);
156
+ this.selection.profile = null;
157
+ }
158
+ this.pending.perm = undefined;
159
+ }
160
+ // Reset model/effort/permission to a newly chosen backend's defaults so the
161
+ // downstream steps never carry a stale value from the previous backend.
162
+ applyBackend(backend) {
163
+ this.selection.backend = backend;
164
+ const models = this.opts.modelsFor(backend);
165
+ this.selection.model = models[0]?.value ?? this.selection.model;
166
+ this.selection.effort = this.opts.defaultEffortFor(backend);
167
+ const perms = this.opts.permsFor(backend);
168
+ this.selection.permMode = (perms[0]?.value ?? this.selection.permMode);
169
+ this.selection.profile = null;
170
+ this.pending = {};
171
+ }
172
+ async start() {
173
+ if (this.selection.cwd === null) {
174
+ // Should not happen (folder is step 1) — guard rather than start with no cwd.
175
+ this.step = 'folder';
176
+ return;
177
+ }
178
+ const result = await this.opts.start({
179
+ guildId: this.opts.guildId,
180
+ channelId: this.opts.channelId,
181
+ mode: this.selection.backend,
182
+ cwd: this.selection.cwd,
183
+ ownerId: this.opts.ownerId,
184
+ permMode: this.selection.permMode,
185
+ profile: this.selection.profile,
186
+ effort: this.selection.effort,
187
+ });
188
+ this.createdChannelId = result.channelId;
189
+ this.step = 'done';
190
+ }
191
+ // The channel id the confirmed session was bound to (the created session channel),
192
+ // or null before a successful start. The router links it back to the driver.
193
+ sessionChannelId() {
194
+ return this.createdChannelId;
195
+ }
196
+ // Render the current step as a plain component spec (embed + rows). 7b maps it onto
197
+ // a discord.js reply/update. Pure data — the tests assert on it directly.
198
+ render() {
199
+ switch (this.step) {
200
+ case 'folder':
201
+ return this.browser.render();
202
+ case 'backend':
203
+ return this.choiceStep('wizard.step.backend', 'backend', this.opts.backends.map((b) => ({
204
+ label: t(`backend.${b}`) === `backend.${b}` ? b : t(`backend.${b}`),
205
+ value: b,
206
+ default: b === (this.pending.backend ?? this.selection.backend),
207
+ })), nextButton('backend.next'));
208
+ case 'model':
209
+ // English labels from the catalog (model id / SDK displayName), not localized.
210
+ return this.choiceStep('wizard.step.model', 'model', this.opts.modelsFor(this.selection.backend).map((m) => ({
211
+ label: m.label,
212
+ value: m.value,
213
+ default: m.value === (this.pending.model ?? this.selection.model),
214
+ })), nextButton('model.next'));
215
+ case 'effort':
216
+ return this.choiceStep('wizard.step.effort', 'effort', this.opts.effortsFor(this.selection.backend, this.selection.model).map((e) => ({
217
+ label: e.label,
218
+ value: e.value,
219
+ default: e.value === (this.pending.effort ?? this.selection.effort),
220
+ })), nextButton('effort.next'));
221
+ case 'perm':
222
+ return this.permStep();
223
+ case 'confirm':
224
+ // The perm step is the final choice; confirm has no dedicated render (start
225
+ // transitions straight to 'done'). Kept only for state completeness.
226
+ return this.permStep();
227
+ case 'done':
228
+ return {
229
+ embed: {
230
+ title: t('wizard.title'),
231
+ description: t('wizard.started', {
232
+ backend: this.selection.backend,
233
+ cwd: this.selection.cwd ?? '',
234
+ }),
235
+ },
236
+ rows: [],
237
+ };
238
+ case 'cancelled':
239
+ return { embed: { title: t('wizard.title'), description: t('wizard.cancelled') }, rows: [] };
240
+ }
241
+ }
242
+ // A choice step: a single-select (pending-aware `default`) + a confirm button that
243
+ // advances, and a cancel button. The button — not the select — drives the transition.
244
+ choiceStep(titleKey, id, options, confirm) {
245
+ const select = { type: 'select', customId: id, placeholder: t(titleKey), options };
246
+ return {
247
+ embed: { title: t('wizard.title'), description: t(titleKey) },
248
+ rows: [{ components: [select] }, { components: [confirm, cancelButton()] }],
249
+ };
250
+ }
251
+ // Permission step (final): a profiles select (Claude quick path, when profiles exist)
252
+ // plus the backend's permission select (Claude PermMode list or Codex sandbox terms),
253
+ // and a '✅ 시작' start button. All selects update pending state; only the button
254
+ // starts the session.
255
+ permStep() {
256
+ const rows = [];
257
+ const pendingProfile = this.pending.perm?.startsWith('profile:')
258
+ ? this.pending.perm.slice('profile:'.length)
259
+ : null;
260
+ const pendingMode = this.pending.perm?.startsWith('mode:')
261
+ ? this.pending.perm.slice('mode:'.length)
262
+ : null;
263
+ if (this.opts.profiles.length > 0) {
264
+ const selectedProfile = pendingProfile ?? this.selection.profile;
265
+ const profileSelect = {
266
+ type: 'select',
267
+ customId: 'perm.profile',
268
+ placeholder: t('wizard.step.perm'),
269
+ options: [
270
+ ...this.opts.profiles.map((p) => ({ label: p, value: p, default: p === selectedProfile })),
271
+ { label: t('wizard.profile.advanced'), value: '__raw__' },
272
+ ],
273
+ };
274
+ rows.push({ components: [profileSelect] });
275
+ }
276
+ const selectedMode = pendingMode ?? this.selection.permMode;
277
+ const modeSelect = {
278
+ type: 'select',
279
+ customId: 'perm.mode',
280
+ placeholder: t('wizard.profile.advanced'),
281
+ // English identifiers + a short English hint from the catalog, per backend.
282
+ options: this.opts.permsFor(this.selection.backend).map((m) => ({
283
+ label: m.label,
284
+ value: m.value,
285
+ default: m.value === selectedMode,
286
+ })),
287
+ };
288
+ rows.push({ components: [modeSelect] });
289
+ rows.push({
290
+ components: [
291
+ { type: 'button', customId: 'perm.start', label: t('wizard.start'), style: 'success' },
292
+ cancelButton(),
293
+ ],
294
+ });
295
+ return { embed: { title: t('wizard.title'), description: t('wizard.step.perm') }, rows };
296
+ }
297
+ }
298
+ // The "다음" (Next) confirm button for a choice step, with the step-specific custom id.
299
+ function nextButton(customId) {
300
+ return { type: 'button', customId, label: t('wizard.next'), style: 'primary' };
301
+ }
302
+ function cancelButton() {
303
+ return { type: 'button', customId: 'cancel', label: t('wizard.cancel'), style: 'secondary' };
304
+ }
305
+ //# sourceMappingURL=channelWizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channelWizard.js","sourceRoot":"","sources":["../../../src/discord/wizard/channelWizard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,298 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { ChannelWizard } from './channelWizard.js';
6
+ import { DirectoryBrowser } from '../directoryBrowser.js';
7
+ import { permissionChoicesFor, effortChoicesFor, defaultEffortFor, codexSandboxChoices, } from '../../core/providerCatalog.js';
8
+ let root;
9
+ beforeEach(() => {
10
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-wizard-'));
11
+ fs.mkdirSync(path.join(root, 'project'));
12
+ });
13
+ afterEach(() => {
14
+ fs.rmSync(root, { recursive: true, force: true });
15
+ });
16
+ function fakeSession() {
17
+ return { sessionId: 'sess-1', async send() { }, async stop() { } };
18
+ }
19
+ // The router creates a dedicated session channel and binds there; the wizard's start
20
+ // callback returns that new channel id alongside the session.
21
+ function fakeStartResult() {
22
+ return { session: fakeSession(), channelId: 'new-session-channel' };
23
+ }
24
+ // Backend-aware option suppliers, mirroring what the router injects.
25
+ const CLAUDE_MODELS = [
26
+ { value: 'opus', label: 'opus' },
27
+ { value: 'sonnet', label: 'sonnet' },
28
+ ];
29
+ const CODEX_MODELS = [
30
+ { value: 'gpt-5.5', label: 'gpt-5.5' },
31
+ { value: 'gpt-5.4', label: 'gpt-5.4' },
32
+ ];
33
+ function makeWizard(start) {
34
+ const browser = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
35
+ return new ChannelWizard({
36
+ guildId: 'g1',
37
+ channelId: 'c1',
38
+ ownerId: 'u1',
39
+ start,
40
+ defaults: { backend: 'claude', model: 'opus', permMode: 'default', profile: null },
41
+ backends: ['claude', 'codex'],
42
+ modelsFor: (b) => (b === 'codex' ? CODEX_MODELS : CLAUDE_MODELS),
43
+ profiles: ['읽기전용', '수정허용'],
44
+ permsFor: (b) => permissionChoicesFor(b),
45
+ effortsFor: (b, model) => effortChoicesFor(b, CLAUDE_MODELS.find((m) => m.value === model)?.supportedEffortLevels),
46
+ defaultEffortFor,
47
+ browser,
48
+ });
49
+ }
50
+ // A wizard with NO profiles, so the permission step shows only the raw/sandbox select.
51
+ function makeWizardNoProfiles(start) {
52
+ const browser = new DirectoryBrowser({ allowedRoots: [root], startPath: root });
53
+ return new ChannelWizard({
54
+ guildId: 'g1',
55
+ channelId: 'c1',
56
+ ownerId: 'u1',
57
+ start,
58
+ defaults: { backend: 'claude', model: 'opus', permMode: 'default', profile: null },
59
+ backends: ['claude', 'codex'],
60
+ modelsFor: (b) => (b === 'codex' ? CODEX_MODELS : CLAUDE_MODELS),
61
+ profiles: [],
62
+ permsFor: (b) => permissionChoicesFor(b),
63
+ effortsFor: (b) => effortChoicesFor(b),
64
+ defaultEffortFor,
65
+ browser,
66
+ });
67
+ }
68
+ function flat(rows) {
69
+ return rows.flatMap((r) => r.components);
70
+ }
71
+ function selectOptions(wizard, customId) {
72
+ const c = flat(wizard.render().rows).find((x) => x.customId === customId);
73
+ return c?.options ?? [];
74
+ }
75
+ describe('ChannelWizard state machine (button-advance, backend-aware)', () => {
76
+ it('advances folder → backend → model → effort → perm and starts on the perm.start button', async () => {
77
+ const start = vi.fn(async (_p) => fakeStartResult());
78
+ const wizard = makeWizard(start);
79
+ expect(wizard.currentStep()).toBe('folder');
80
+ // Folder: descend into 'project' then select it.
81
+ await wizard.handle({ id: 'dir:into', value: 'project' });
82
+ expect(wizard.current().cwd).toBeNull(); // not selected yet
83
+ expect(await wizard.handle({ id: 'dir:here' })).toBe('backend');
84
+ expect(wizard.current().cwd).toBe(path.join(root, 'project'));
85
+ // Backend: a select-change does NOT advance; the 다음 button does.
86
+ expect(await wizard.handle({ id: 'backend', value: 'codex' })).toBe('backend');
87
+ expect(wizard.current().backend).toBe('claude'); // not committed yet
88
+ expect(await wizard.handle({ id: 'backend.next' })).toBe('model');
89
+ expect(wizard.current().backend).toBe('codex');
90
+ // Model
91
+ expect(await wizard.handle({ id: 'model', value: 'gpt-5.4' })).toBe('model'); // no advance on change
92
+ expect(await wizard.handle({ id: 'model.next' })).toBe('effort');
93
+ expect(wizard.current().model).toBe('gpt-5.4');
94
+ // Reasoning effort
95
+ expect(await wizard.handle({ id: 'effort', value: 'high' })).toBe('effort'); // no advance on change
96
+ expect(await wizard.handle({ id: 'effort.next' })).toBe('perm');
97
+ expect(wizard.current().effort).toBe('high');
98
+ // Permission — pick a profile (Claude quick path), then the ✅ 시작 button starts.
99
+ expect(await wizard.handle({ id: 'perm.profile', value: '수정허용' })).toBe('perm'); // no advance on change
100
+ expect(await wizard.handle({ id: 'perm.start' })).toBe('done');
101
+ expect(wizard.current().profile).toBe('수정허용');
102
+ expect(start).toHaveBeenCalledTimes(1);
103
+ expect(start).toHaveBeenCalledWith({
104
+ guildId: 'g1',
105
+ channelId: 'c1',
106
+ mode: 'codex',
107
+ cwd: path.join(root, 'project'),
108
+ ownerId: 'u1',
109
+ // Switching to codex reset permMode to codex's first sandbox mode (read-only); the
110
+ // profile pick rides its own channel and does not change permMode.
111
+ permMode: 'read-only',
112
+ profile: '수정허용',
113
+ effort: 'high',
114
+ });
115
+ });
116
+ // REGRESSION for the reported bug: keeping the pre-selected default and pressing the
117
+ // step's button (WITHOUT any select-change) must advance. Discord does not re-fire a
118
+ // select interaction for the already-selected option, so advancing on change alone
119
+ // left a user who kept the default stuck.
120
+ it('keeping every default and pressing the buttons advances all the way to start', async () => {
121
+ const start = vi.fn(async (_p) => fakeStartResult());
122
+ const wizard = makeWizardNoProfiles(start);
123
+ expect(await wizard.handle({ id: 'dir:here' })).toBe('backend');
124
+ // No select-change on any step — only the confirm buttons.
125
+ expect(await wizard.handle({ id: 'backend.next' })).toBe('model');
126
+ expect(await wizard.handle({ id: 'model.next' })).toBe('effort');
127
+ expect(await wizard.handle({ id: 'effort.next' })).toBe('perm');
128
+ expect(await wizard.handle({ id: 'perm.start' })).toBe('done');
129
+ expect(start).toHaveBeenCalledOnce();
130
+ // Defaults carried through: claude backend, high effort, default permission, no profile.
131
+ // (The model is threaded via resolved config, not the start params.)
132
+ expect(start).toHaveBeenCalledWith(expect.objectContaining({ mode: 'claude', effort: 'high', permMode: 'default', profile: null }));
133
+ });
134
+ it('a select-change updates PENDING state (shown selected) without advancing the step', async () => {
135
+ const start = vi.fn(async (_p) => fakeStartResult());
136
+ const wizard = makeWizardNoProfiles(start);
137
+ await wizard.handle({ id: 'dir:here' });
138
+ // Change the backend select to codex — step stays 'backend' and the option renders
139
+ // as default-selected, but the committed backend is still claude.
140
+ await wizard.handle({ id: 'backend', value: 'codex' });
141
+ expect(wizard.currentStep()).toBe('backend');
142
+ expect(wizard.current().backend).toBe('claude');
143
+ const codexOpt = selectOptions(wizard, 'backend').find((o) => o.value === 'codex');
144
+ expect(codexOpt?.default).toBe(true);
145
+ });
146
+ it('after selecting Codex, the model step shows CODEX models and the perm step shows CODEX sandbox terms', async () => {
147
+ const start = vi.fn(async (_p) => fakeStartResult());
148
+ const wizard = makeWizardNoProfiles(start);
149
+ await wizard.handle({ id: 'dir:here' });
150
+ await wizard.handle({ id: 'backend', value: 'codex' });
151
+ await wizard.handle({ id: 'backend.next' }); // commit codex → model step
152
+ // Model options are the Codex models, not Claude's.
153
+ expect(selectOptions(wizard, 'model').map((o) => o.value)).toEqual(['gpt-5.5', 'gpt-5.4']);
154
+ await wizard.handle({ id: 'model.next' }); // effort step
155
+ // Codex effort options include 'minimal' (Codex-only) and NOT Claude's 'max'.
156
+ const effortValues = selectOptions(wizard, 'effort').map((o) => o.value);
157
+ expect(effortValues).toContain('minimal');
158
+ expect(effortValues).not.toContain('max');
159
+ await wizard.handle({ id: 'effort.next' }); // perm step
160
+ // Permission options are Codex sandbox terms (perm.mode select), not Claude modes.
161
+ const permValues = selectOptions(wizard, 'perm.mode').map((o) => o.value);
162
+ expect(permValues).toEqual(['read-only', 'workspace-write', 'danger-full-access']);
163
+ expect(permValues).not.toContain('acceptEdits');
164
+ });
165
+ it('after selecting Claude, the model step shows CLAUDE models and the perm step shows CLAUDE modes', async () => {
166
+ const start = vi.fn(async (_p) => fakeStartResult());
167
+ const wizard = makeWizardNoProfiles(start);
168
+ await wizard.handle({ id: 'dir:here' });
169
+ await wizard.handle({ id: 'backend.next' }); // keep claude
170
+ expect(selectOptions(wizard, 'model').map((o) => o.value)).toEqual(['opus', 'sonnet']);
171
+ await wizard.handle({ id: 'model.next' });
172
+ // Claude effort options include 'max' and NOT Codex's 'minimal'.
173
+ const effortValues = selectOptions(wizard, 'effort').map((o) => o.value);
174
+ expect(effortValues).toContain('max');
175
+ expect(effortValues).not.toContain('minimal');
176
+ await wizard.handle({ id: 'effort.next' });
177
+ const permValues = selectOptions(wizard, 'perm.mode').map((o) => o.value);
178
+ expect(permValues).toContain('acceptEdits');
179
+ expect(permValues).toContain('dontAsk');
180
+ expect(permValues).not.toContain('workspace-write');
181
+ });
182
+ it('a Codex sandbox permission pick rides the permMode channel to start', async () => {
183
+ const start = vi.fn(async (_p) => fakeStartResult());
184
+ const wizard = makeWizardNoProfiles(start);
185
+ await wizard.handle({ id: 'dir:here' });
186
+ await wizard.handle({ id: 'backend', value: 'codex' });
187
+ await wizard.handle({ id: 'backend.next' });
188
+ await wizard.handle({ id: 'model.next' });
189
+ await wizard.handle({ id: 'effort.next' });
190
+ await wizard.handle({ id: 'perm.mode', value: 'danger-full-access' });
191
+ await wizard.handle({ id: 'perm.start' });
192
+ expect(start).toHaveBeenCalledWith(expect.objectContaining({ mode: 'codex', permMode: 'danger-full-access', profile: null }));
193
+ });
194
+ it('advanced path: a raw permission mode clears any profile', async () => {
195
+ const start = vi.fn(async (_p) => fakeStartResult());
196
+ const wizard = makeWizard(start);
197
+ await wizard.handle({ id: 'dir:here' }); // select root as cwd
198
+ await wizard.handle({ id: 'backend.next' }); // keep claude
199
+ await wizard.handle({ id: 'model.next' }); // keep opus
200
+ await wizard.handle({ id: 'effort.next' }); // keep high
201
+ await wizard.handle({ id: 'perm.mode', value: 'plan' });
202
+ await wizard.handle({ id: 'perm.start' });
203
+ expect(start).toHaveBeenCalledWith(expect.objectContaining({ mode: 'claude', permMode: 'plan', profile: null }));
204
+ });
205
+ it('exposes the created session channel id after start', async () => {
206
+ const start = vi.fn(async (_p) => fakeStartResult());
207
+ const wizard = makeWizardNoProfiles(start);
208
+ await wizard.handle({ id: 'dir:here' });
209
+ await wizard.handle({ id: 'backend.next' });
210
+ await wizard.handle({ id: 'model.next' });
211
+ await wizard.handle({ id: 'effort.next' });
212
+ expect(wizard.sessionChannelId()).toBeNull(); // not started yet
213
+ await wizard.handle({ id: 'perm.start' });
214
+ expect(wizard.sessionChannelId()).toBe('new-session-channel');
215
+ });
216
+ it('cancel from any step ends the flow without starting', async () => {
217
+ const start = vi.fn(async (_p) => fakeStartResult());
218
+ const wizard = makeWizard(start);
219
+ await wizard.handle({ id: 'dir:here' });
220
+ expect(await wizard.handle({ id: 'cancel' })).toBe('cancelled');
221
+ expect(start).not.toHaveBeenCalled();
222
+ });
223
+ it('ignores a stray input for the current step', async () => {
224
+ const start = vi.fn(async (_p) => fakeStartResult());
225
+ const wizard = makeWizard(start);
226
+ // A 'backend' input during the folder step is ignored.
227
+ expect(await wizard.handle({ id: 'backend', value: 'codex' })).toBe('folder');
228
+ expect(wizard.current().backend).toBe('claude'); // unchanged default
229
+ });
230
+ });
231
+ describe('ChannelWizard render (step guidance + labels + buttons)', () => {
232
+ function componentsOf(rows) {
233
+ return rows.flatMap((r) => r.components);
234
+ }
235
+ it('the folder step renders the browser guidance + current path, and a ✅ start button', async () => {
236
+ const start = vi.fn(async (_p) => fakeStartResult());
237
+ const wizard = makeWizard(start);
238
+ const { embed, rows } = wizard.render();
239
+ expect(embed.description).toContain('프로젝트 폴더');
240
+ expect(embed.description).toContain(root);
241
+ const dirHere = componentsOf(rows).find((c) => c.customId === 'dir:here');
242
+ expect(dirHere?.label).toContain('시작');
243
+ });
244
+ it('each choice step carries a "다음" button (backend/model/effort) so the button advances, not the select', async () => {
245
+ const start = vi.fn(async (_p) => fakeStartResult());
246
+ const wizard = makeWizardNoProfiles(start);
247
+ await wizard.handle({ id: 'dir:here' });
248
+ // backend step
249
+ expect(componentsOf(wizard.render().rows).find((c) => c.customId === 'backend.next')?.label).toBe('다음');
250
+ await wizard.handle({ id: 'backend.next' });
251
+ // model step
252
+ expect(componentsOf(wizard.render().rows).find((c) => c.customId === 'model.next')?.label).toBe('다음');
253
+ await wizard.handle({ id: 'model.next' });
254
+ // effort step
255
+ expect(componentsOf(wizard.render().rows).find((c) => c.customId === 'effort.next')?.label).toBe('다음');
256
+ });
257
+ it('the permission step (final) uses a ✅ 시작 button (perm.start), not a 다음 button', async () => {
258
+ const start = vi.fn(async (_p) => fakeStartResult());
259
+ const wizard = makeWizard(start);
260
+ await wizard.handle({ id: 'dir:here' });
261
+ await wizard.handle({ id: 'backend.next' });
262
+ await wizard.handle({ id: 'model.next' });
263
+ await wizard.handle({ id: 'effort.next' });
264
+ expect(wizard.currentStep()).toBe('perm');
265
+ const startBtn = componentsOf(wizard.render().rows).find((c) => c.customId === 'perm.start');
266
+ expect(startBtn?.label).toContain('시작');
267
+ // The cancel button is a short label, not a full sentence.
268
+ const cancel = componentsOf(wizard.render().rows).find((c) => c.customId === 'cancel');
269
+ expect(cancel?.label).toBe('취소');
270
+ });
271
+ it('the model + permission OPTION labels are the original ENGLISH (no Korean)', async () => {
272
+ const start = vi.fn(async (_p) => fakeStartResult());
273
+ const wizard = makeWizardNoProfiles(start);
274
+ await wizard.handle({ id: 'dir:here' });
275
+ await wizard.handle({ id: 'backend.next' }); // claude
276
+ expect(selectOptions(wizard, 'model').map((o) => o.label)).toEqual(['opus', 'sonnet']);
277
+ await wizard.handle({ id: 'model.next' });
278
+ await wizard.handle({ id: 'effort.next' });
279
+ const permSelect = selectOptions(wizard, 'perm.mode');
280
+ const byValue = (v) => permSelect.find((o) => o.value === v);
281
+ expect(byValue('default')?.label).toBe('default (ask each time)');
282
+ expect(byValue('plan')?.label).toBe('plan (read-only planning)');
283
+ const hangul = /[가-힣]/;
284
+ for (const o of permSelect)
285
+ expect(hangul.test(o.label)).toBe(false);
286
+ });
287
+ });
288
+ // Guard the exported Codex sandbox catalog stays the three documented sandbox modes.
289
+ describe('codexSandboxChoices', () => {
290
+ it('offers exactly read-only / workspace-write / danger-full-access with English labels', () => {
291
+ const choices = codexSandboxChoices();
292
+ expect(choices.map((c) => c.value)).toEqual(['read-only', 'workspace-write', 'danger-full-access']);
293
+ const hangul = /[가-힣]/;
294
+ for (const c of choices)
295
+ expect(hangul.test(c.label)).toBe(false);
296
+ });
297
+ });
298
+ //# sourceMappingURL=channelWizard.test.js.map