discord-agent-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/app.js +210 -0
- package/dist/app.js.map +1 -0
- package/dist/app.test.js +117 -0
- package/dist/app.test.js.map +1 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.js +91 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/core/auditLog.js +65 -0
- package/dist/core/auditLog.js.map +1 -0
- package/dist/core/auditLog.test.js +124 -0
- package/dist/core/auditLog.test.js.map +1 -0
- package/dist/core/auth.js +106 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/auth.test.js +216 -0
- package/dist/core/auth.test.js.map +1 -0
- package/dist/core/channelRegistry.js +119 -0
- package/dist/core/channelRegistry.js.map +1 -0
- package/dist/core/channelRegistry.test.js +114 -0
- package/dist/core/channelRegistry.test.js.map +1 -0
- package/dist/core/commandPolicy.js +6 -0
- package/dist/core/commandPolicy.js.map +1 -0
- package/dist/core/commandRouter.js +9 -0
- package/dist/core/commandRouter.js.map +1 -0
- package/dist/core/config.js +141 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.js +149 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/configResolver.js +90 -0
- package/dist/core/configResolver.js.map +1 -0
- package/dist/core/configResolver.test.js +138 -0
- package/dist/core/configResolver.test.js.map +1 -0
- package/dist/core/configSchema.js +164 -0
- package/dist/core/configSchema.js.map +1 -0
- package/dist/core/contracts.js +5 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/eventBus.js +42 -0
- package/dist/core/eventBus.js.map +1 -0
- package/dist/core/eventBus.test.js +72 -0
- package/dist/core/eventBus.test.js.map +1 -0
- package/dist/core/hookBridge.js +11 -0
- package/dist/core/hookBridge.js.map +1 -0
- package/dist/core/logger.js +116 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/logger.test.js +88 -0
- package/dist/core/logger.test.js.map +1 -0
- package/dist/core/modeRegistry.js +33 -0
- package/dist/core/modeRegistry.js.map +1 -0
- package/dist/core/permissionResolver.js +43 -0
- package/dist/core/permissionResolver.js.map +1 -0
- package/dist/core/permissionResolver.test.js +136 -0
- package/dist/core/permissionResolver.test.js.map +1 -0
- package/dist/core/providerCatalog.js +240 -0
- package/dist/core/providerCatalog.js.map +1 -0
- package/dist/core/providerCatalog.test.js +230 -0
- package/dist/core/providerCatalog.test.js.map +1 -0
- package/dist/core/sessionOrchestrator.js +463 -0
- package/dist/core/sessionOrchestrator.js.map +1 -0
- package/dist/core/sessionOrchestrator.test.js +344 -0
- package/dist/core/sessionOrchestrator.test.js.map +1 -0
- package/dist/core/state/schema.js +49 -0
- package/dist/core/state/schema.js.map +1 -0
- package/dist/core/state/store.js +86 -0
- package/dist/core/state/store.js.map +1 -0
- package/dist/core/state/store.test.js +99 -0
- package/dist/core/state/store.test.js.map +1 -0
- package/dist/core/usageService.js +310 -0
- package/dist/core/usageService.js.map +1 -0
- package/dist/core/usageService.test.js +234 -0
- package/dist/core/usageService.test.js.map +1 -0
- package/dist/discord/alwaysAllow.test.js +181 -0
- package/dist/discord/alwaysAllow.test.js.map +1 -0
- package/dist/discord/client.js +666 -0
- package/dist/discord/client.js.map +1 -0
- package/dist/discord/client.test.js +256 -0
- package/dist/discord/client.test.js.map +1 -0
- package/dist/discord/configPanel.js +270 -0
- package/dist/discord/configPanel.js.map +1 -0
- package/dist/discord/configPanel.test.js +332 -0
- package/dist/discord/configPanel.test.js.map +1 -0
- package/dist/discord/directoryBrowser.js +151 -0
- package/dist/discord/directoryBrowser.js.map +1 -0
- package/dist/discord/directoryBrowser.test.js +163 -0
- package/dist/discord/directoryBrowser.test.js.map +1 -0
- package/dist/discord/favorites.js +10 -0
- package/dist/discord/favorites.js.map +1 -0
- package/dist/discord/fileDownload.js +94 -0
- package/dist/discord/fileDownload.js.map +1 -0
- package/dist/discord/fileDownload.test.js +59 -0
- package/dist/discord/fileDownload.test.js.map +1 -0
- package/dist/discord/format.js +104 -0
- package/dist/discord/format.js.map +1 -0
- package/dist/discord/format.test.js +40 -0
- package/dist/discord/format.test.js.map +1 -0
- package/dist/discord/guildChannels.js +97 -0
- package/dist/discord/guildChannels.js.map +1 -0
- package/dist/discord/guildChannels.test.js +231 -0
- package/dist/discord/guildChannels.test.js.map +1 -0
- package/dist/discord/i18n.js +207 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/i18n.test.js +31 -0
- package/dist/discord/i18n.test.js.map +1 -0
- package/dist/discord/interactionRouter.js +954 -0
- package/dist/discord/interactionRouter.js.map +1 -0
- package/dist/discord/interactionRouter.test.js +1245 -0
- package/dist/discord/interactionRouter.test.js.map +1 -0
- package/dist/discord/messageRouter.js +221 -0
- package/dist/discord/messageRouter.js.map +1 -0
- package/dist/discord/messageRouter.test.js +360 -0
- package/dist/discord/messageRouter.test.js.map +1 -0
- package/dist/discord/ports.js +12 -0
- package/dist/discord/ports.js.map +1 -0
- package/dist/discord/renderers/defaultSet.test.js +97 -0
- package/dist/discord/renderers/defaultSet.test.js.map +1 -0
- package/dist/discord/renderers/diffView.js +73 -0
- package/dist/discord/renderers/diffView.js.map +1 -0
- package/dist/discord/renderers/diffView.test.js +54 -0
- package/dist/discord/renderers/diffView.test.js.map +1 -0
- package/dist/discord/renderers/index.js +143 -0
- package/dist/discord/renderers/index.js.map +1 -0
- package/dist/discord/renderers/index.test.js +124 -0
- package/dist/discord/renderers/index.test.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.js +19 -0
- package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
- package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.js +150 -0
- package/dist/discord/renderers/permissionButtons.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.test.js +108 -0
- package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
- package/dist/discord/renderers/resultLine.js +25 -0
- package/dist/discord/renderers/resultLine.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.js +20 -0
- package/dist/discord/renderers/statusEmbed.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.test.js +32 -0
- package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.js +134 -0
- package/dist/discord/renderers/streamEmbed.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.test.js +97 -0
- package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
- package/dist/discord/renderers/toolThread.js +65 -0
- package/dist/discord/renderers/toolThread.js.map +1 -0
- package/dist/discord/renderers/toolThread.test.js +54 -0
- package/dist/discord/renderers/toolThread.test.js.map +1 -0
- package/dist/discord/renderers/transcriptFeed.js +33 -0
- package/dist/discord/renderers/transcriptFeed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.js +78 -0
- package/dist/discord/renderers/usageEmbed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.test.js +42 -0
- package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
- package/dist/discord/wiring.js +214 -0
- package/dist/discord/wiring.js.map +1 -0
- package/dist/discord/wiring.test.js +252 -0
- package/dist/discord/wiring.test.js.map +1 -0
- package/dist/discord/wizard/channelWizard.js +305 -0
- package/dist/discord/wizard/channelWizard.js.map +1 -0
- package/dist/discord/wizard/channelWizard.test.js +298 -0
- package/dist/discord/wizard/channelWizard.test.js.map +1 -0
- package/dist/discord/wizard/resumeWizard.js +123 -0
- package/dist/discord/wizard/resumeWizard.js.map +1 -0
- package/dist/modes/claude/index.js +80 -0
- package/dist/modes/claude/index.js.map +1 -0
- package/dist/modes/claude/listResumable.test.js +72 -0
- package/dist/modes/claude/listResumable.test.js.map +1 -0
- package/dist/modes/claude/mcpFileTool.js +82 -0
- package/dist/modes/claude/mcpFileTool.js.map +1 -0
- package/dist/modes/claude/permissions.js +35 -0
- package/dist/modes/claude/permissions.js.map +1 -0
- package/dist/modes/claude/plugins.js +56 -0
- package/dist/modes/claude/plugins.js.map +1 -0
- package/dist/modes/claude/session.js +324 -0
- package/dist/modes/claude/session.js.map +1 -0
- package/dist/modes/claude/session.test.js +415 -0
- package/dist/modes/claude/session.test.js.map +1 -0
- package/dist/modes/codex/discovery.js +221 -0
- package/dist/modes/codex/discovery.js.map +1 -0
- package/dist/modes/codex/discovery.test.js +213 -0
- package/dist/modes/codex/discovery.test.js.map +1 -0
- package/dist/modes/codex/eventMapper.js +262 -0
- package/dist/modes/codex/eventMapper.js.map +1 -0
- package/dist/modes/codex/index.js +132 -0
- package/dist/modes/codex/index.js.map +1 -0
- package/dist/modes/codex/index.test.js +170 -0
- package/dist/modes/codex/index.test.js.map +1 -0
- package/dist/modes/codex/runner.js +207 -0
- package/dist/modes/codex/runner.js.map +1 -0
- package/dist/modes/codex/runner.test.js +587 -0
- package/dist/modes/codex/runner.test.js.map +1 -0
- package/dist/modes/codex/sqliteReader.js +171 -0
- package/dist/modes/codex/sqliteReader.js.map +1 -0
- package/dist/modes/codex/sqliteReader.test.js +137 -0
- package/dist/modes/codex/sqliteReader.test.js.map +1 -0
- package/dist/setup/wizard.js +115 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/setup/wizard.test.js +216 -0
- package/dist/setup/wizard.test.js.map +1 -0
- package/dist/startBot.test.js +78 -0
- package/dist/startBot.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,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
|