wave-code 0.19.7 → 0.19.9
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/dist/cli.js +1 -1
- package/dist/components/ConfirmationSelector.js +4 -3
- package/dist/components/TaskList.js +28 -12
- package/dist/contexts/useChat.js +1 -1
- package/dist/index.js +3 -2
- package/dist/print-cli.js +12 -4
- package/dist/reducers/confirmationReducer.d.ts +1 -1
- package/dist/reducers/confirmationReducer.js +9 -10
- package/dist/stdio/agentBridge.d.ts +11 -0
- package/dist/stdio/agentBridge.js +178 -1
- package/dist/stdio/protocol.d.ts +2 -2
- package/dist/utils/worktree.d.ts +8 -2
- package/dist/utils/worktree.js +35 -26
- package/package.json +2 -2
- package/src/cli.tsx +1 -1
- package/src/components/ConfirmationSelector.tsx +16 -15
- package/src/components/TaskList.tsx +30 -12
- package/src/contexts/useChat.tsx +1 -1
- package/src/index.ts +8 -2
- package/src/print-cli.ts +14 -4
- package/src/reducers/confirmationReducer.ts +9 -9
- package/src/stdio/agentBridge.ts +282 -0
- package/src/stdio/protocol.ts +14 -2
- package/src/utils/worktree.ts +57 -33
package/dist/utils/worktree.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import { getDefaultRemoteBranch, getGitMainRepoRoot } from "wave-agent-sdk";
|
|
6
|
+
// Never use execFileSync here: the shared `wave --stdio` process handles all
|
|
7
|
+
// desktop sessions, so a synchronous git call (especially a multi-second
|
|
8
|
+
// recursive worktree delete or a network fetch) freezes every session.
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
5
10
|
/**
|
|
6
11
|
* Create a new git worktree
|
|
7
12
|
* @param name Worktree name
|
|
8
13
|
* @param cwd Current working directory
|
|
14
|
+
* @param options Optional creation options
|
|
15
|
+
* @param options.baseRef "fresh" (default, origin/<default-branch>) | "head" (local HEAD)
|
|
16
|
+
* @param options.baseBranch Explicit base branch (overrides baseRef)
|
|
9
17
|
* @returns Worktree session details
|
|
10
18
|
*/
|
|
11
|
-
export function createWorktree(name, cwd) {
|
|
19
|
+
export async function createWorktree(name, cwd, options) {
|
|
12
20
|
const repoRoot = getGitMainRepoRoot(cwd);
|
|
13
21
|
const worktreePath = path.join(repoRoot, ".wave", "worktrees", name);
|
|
14
22
|
const branchName = `worktree-${name}`;
|
|
15
|
-
const
|
|
23
|
+
const useHead = options?.baseRef === "head";
|
|
24
|
+
const resolvedBaseBranch = options?.baseBranch ?? (useHead ? "HEAD" : getDefaultRemoteBranch(cwd));
|
|
16
25
|
// Ensure parent directory exists
|
|
17
26
|
const parentDir = path.dirname(worktreePath);
|
|
18
27
|
if (!fs.existsSync(parentDir)) {
|
|
@@ -33,9 +42,8 @@ export function createWorktree(name, cwd) {
|
|
|
33
42
|
}
|
|
34
43
|
try {
|
|
35
44
|
// Create worktree and branch
|
|
36
|
-
|
|
45
|
+
await execFileAsync("git", ["worktree", "add", "-b", branchName, worktreePath, resolvedBaseBranch], {
|
|
37
46
|
cwd: repoRoot,
|
|
38
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
39
47
|
});
|
|
40
48
|
return {
|
|
41
49
|
name,
|
|
@@ -52,9 +60,8 @@ export function createWorktree(name, cwd) {
|
|
|
52
60
|
if (stderr.includes("already exists")) {
|
|
53
61
|
// If branch already exists, try to add worktree without -b
|
|
54
62
|
try {
|
|
55
|
-
|
|
63
|
+
await execFileAsync("git", ["worktree", "add", worktreePath, branchName], {
|
|
56
64
|
cwd: repoRoot,
|
|
57
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
58
65
|
});
|
|
59
66
|
return {
|
|
60
67
|
name,
|
|
@@ -70,18 +77,24 @@ export function createWorktree(name, cwd) {
|
|
|
70
77
|
throw new Error(`Failed to add existing worktree branch: ${innerError.message}`);
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
|
-
if (
|
|
74
|
-
stderr.includes("
|
|
80
|
+
if (!useHead &&
|
|
81
|
+
(stderr.includes("not a valid object name") ||
|
|
82
|
+
stderr.includes("unknown revision"))) {
|
|
75
83
|
// Base branch not fetched yet — try fetching then retrying
|
|
76
|
-
const branchNameOnly =
|
|
84
|
+
const branchNameOnly = resolvedBaseBranch.split("/").pop();
|
|
77
85
|
try {
|
|
78
|
-
|
|
86
|
+
await execFileAsync("git", ["fetch", "origin", branchNameOnly], {
|
|
79
87
|
cwd: repoRoot,
|
|
80
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
81
88
|
});
|
|
82
|
-
|
|
89
|
+
await execFileAsync("git", [
|
|
90
|
+
"worktree",
|
|
91
|
+
"add",
|
|
92
|
+
"-b",
|
|
93
|
+
branchName,
|
|
94
|
+
worktreePath,
|
|
95
|
+
resolvedBaseBranch,
|
|
96
|
+
], {
|
|
83
97
|
cwd: repoRoot,
|
|
84
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
85
98
|
});
|
|
86
99
|
return {
|
|
87
100
|
name,
|
|
@@ -96,9 +109,8 @@ export function createWorktree(name, cwd) {
|
|
|
96
109
|
catch {
|
|
97
110
|
// Fetch or retry failed — fall back to HEAD
|
|
98
111
|
try {
|
|
99
|
-
|
|
112
|
+
await execFileAsync("git", ["worktree", "add", "-b", branchName, worktreePath, "HEAD"], {
|
|
100
113
|
cwd: repoRoot,
|
|
101
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
102
114
|
});
|
|
103
115
|
return {
|
|
104
116
|
name,
|
|
@@ -122,31 +134,29 @@ export function createWorktree(name, cwd) {
|
|
|
122
134
|
* Remove a git worktree and its associated branch
|
|
123
135
|
* @param session Worktree session details
|
|
124
136
|
*/
|
|
125
|
-
export function removeWorktree(session) {
|
|
137
|
+
export async function removeWorktree(session) {
|
|
126
138
|
const repoRoot = session.repoRoot;
|
|
127
139
|
try {
|
|
128
140
|
// Get current branch in worktree before removing it
|
|
129
141
|
let currentBranch;
|
|
130
142
|
try {
|
|
131
|
-
|
|
143
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
132
144
|
cwd: session.path,
|
|
133
145
|
encoding: "utf8",
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
});
|
|
147
|
+
currentBranch = stdout.trim();
|
|
136
148
|
}
|
|
137
149
|
catch {
|
|
138
150
|
// Ignore errors getting current branch
|
|
139
151
|
}
|
|
140
152
|
// Remove worktree
|
|
141
|
-
|
|
153
|
+
await execFileAsync("git", ["worktree", "remove", "--force", session.path], {
|
|
142
154
|
cwd: repoRoot,
|
|
143
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
144
155
|
});
|
|
145
156
|
// Delete original branch
|
|
146
157
|
try {
|
|
147
|
-
|
|
158
|
+
await execFileAsync("git", ["branch", "-D", session.branch], {
|
|
148
159
|
cwd: repoRoot,
|
|
149
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
150
160
|
});
|
|
151
161
|
}
|
|
152
162
|
catch {
|
|
@@ -162,9 +172,8 @@ export function removeWorktree(session) {
|
|
|
162
172
|
currentBranch !== "main" &&
|
|
163
173
|
currentBranch !== "master") {
|
|
164
174
|
try {
|
|
165
|
-
|
|
175
|
+
await execFileAsync("git", ["branch", "-D", currentBranch], {
|
|
166
176
|
cwd: repoRoot,
|
|
167
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
168
177
|
});
|
|
169
178
|
}
|
|
170
179
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.9",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"semver": "^7.7.4",
|
|
42
42
|
"yargs": "^17.7.2",
|
|
43
43
|
"zod": "^3.23.8",
|
|
44
|
-
"wave-agent-sdk": "0.19.
|
|
44
|
+
"wave-agent-sdk": "0.19.9"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/react": "^19.1.8",
|
package/src/cli.tsx
CHANGED
|
@@ -70,7 +70,7 @@ export async function startCli(options: CliOptions): Promise<void> {
|
|
|
70
70
|
// Cleanup worktree if requested
|
|
71
71
|
if (shouldRemoveWorktree && worktreeSession) {
|
|
72
72
|
process.chdir(worktreeSession.repoRoot);
|
|
73
|
-
removeWorktree(worktreeSession);
|
|
73
|
+
await removeWorktree(worktreeSession);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
process.exit(0);
|
|
@@ -39,7 +39,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
39
39
|
onCancel,
|
|
40
40
|
}) => {
|
|
41
41
|
const [state, dispatch] = useReducer(confirmationReducer, {
|
|
42
|
-
selectedOption:
|
|
42
|
+
selectedOption: "allow",
|
|
43
43
|
alternativeText: "",
|
|
44
44
|
alternativeCursorPosition: 0,
|
|
45
45
|
hasUserInput: false,
|
|
@@ -212,20 +212,6 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
212
212
|
<Text>Do you want to proceed?</Text>
|
|
213
213
|
</Box>
|
|
214
214
|
<Box marginTop={1} flexDirection="column">
|
|
215
|
-
{toolName === EXIT_PLAN_MODE_TOOL_NAME && (
|
|
216
|
-
<Box key="clear-option">
|
|
217
|
-
<Text
|
|
218
|
-
color={state.selectedOption === "clear" ? "black" : "white"}
|
|
219
|
-
backgroundColor={
|
|
220
|
-
state.selectedOption === "clear" ? "yellow" : undefined
|
|
221
|
-
}
|
|
222
|
-
bold={state.selectedOption === "clear"}
|
|
223
|
-
>
|
|
224
|
-
{state.selectedOption === "clear" ? "> " : " "}
|
|
225
|
-
Yes, clear context and auto-accept edits
|
|
226
|
-
</Text>
|
|
227
|
-
</Box>
|
|
228
|
-
)}
|
|
229
215
|
<Box key="allow-option">
|
|
230
216
|
<Text
|
|
231
217
|
color={state.selectedOption === "allow" ? "black" : "white"}
|
|
@@ -254,6 +240,21 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
254
240
|
</Text>
|
|
255
241
|
</Box>
|
|
256
242
|
)}
|
|
243
|
+
{(toolName === BASH_TOOL_NAME ||
|
|
244
|
+
toolName === EXIT_PLAN_MODE_TOOL_NAME) && (
|
|
245
|
+
<Box key="bypass-option">
|
|
246
|
+
<Text
|
|
247
|
+
color={state.selectedOption === "bypass" ? "black" : "white"}
|
|
248
|
+
backgroundColor={
|
|
249
|
+
state.selectedOption === "bypass" ? "yellow" : undefined
|
|
250
|
+
}
|
|
251
|
+
bold={state.selectedOption === "bypass"}
|
|
252
|
+
>
|
|
253
|
+
{state.selectedOption === "bypass" ? "> " : " "}
|
|
254
|
+
Yes, and bypass permissions
|
|
255
|
+
</Text>
|
|
256
|
+
</Box>
|
|
257
|
+
)}
|
|
257
258
|
<Box key="alternative-option">
|
|
258
259
|
<Text
|
|
259
260
|
color={
|
|
@@ -99,12 +99,9 @@ export const TaskList: React.FC = () => {
|
|
|
99
99
|
const autoHideTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(
|
|
100
100
|
null,
|
|
101
101
|
);
|
|
102
|
-
const [autoHidden, setAutoHidden] = React.useState(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const active = tasks.filter((t) => t.status !== "deleted");
|
|
106
|
-
return active.length > 0 && active.every((t) => t.status === "completed");
|
|
107
|
-
});
|
|
102
|
+
const [autoHidden, setAutoHidden] = React.useState(false);
|
|
103
|
+
// 是否在当前展示期间观察到过未完成任务;用于区分“任务在眼前完成”与“加载时已全部完成”
|
|
104
|
+
const hadIncompleteRef = React.useRef(false);
|
|
108
105
|
const [, forceUpdate] = React.useState(0);
|
|
109
106
|
|
|
110
107
|
const now = Date.now();
|
|
@@ -155,24 +152,45 @@ export const TaskList: React.FC = () => {
|
|
|
155
152
|
activeTasks.length > 0 &&
|
|
156
153
|
activeTasks.every((t) => t.status === "completed");
|
|
157
154
|
|
|
155
|
+
// 观察到任务从“存在未完成”变为“全部完成”时保留 5 秒再隐藏;
|
|
156
|
+
// 加载或恢复到已全部完成的会话时立即隐藏,避免先闪现再消失
|
|
158
157
|
React.useEffect(() => {
|
|
159
|
-
if (
|
|
158
|
+
if (activeTasks.length === 0) {
|
|
159
|
+
hadIncompleteRef.current = false;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!allCompleted) {
|
|
163
|
+
hadIncompleteRef.current = true;
|
|
164
|
+
if (autoHidden) {
|
|
165
|
+
setAutoHidden(false);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!hadIncompleteRef.current) {
|
|
170
|
+
if (!autoHidden) {
|
|
171
|
+
setAutoHidden(true);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!autoHidden) {
|
|
160
176
|
autoHideTimerRef.current = setTimeout(() => {
|
|
161
177
|
setAutoHidden(true);
|
|
162
178
|
}, AUTO_HIDE_DELAY_MS);
|
|
163
179
|
}
|
|
164
|
-
if (!allCompleted && autoHidden) {
|
|
165
|
-
setAutoHidden(false);
|
|
166
|
-
}
|
|
167
180
|
return () => {
|
|
168
181
|
if (autoHideTimerRef.current) {
|
|
169
182
|
clearTimeout(autoHideTimerRef.current);
|
|
170
183
|
autoHideTimerRef.current = null;
|
|
171
184
|
}
|
|
172
185
|
};
|
|
173
|
-
}, [allCompleted, autoHidden]);
|
|
186
|
+
}, [allCompleted, autoHidden, activeTasks.length]);
|
|
174
187
|
|
|
175
|
-
if (
|
|
188
|
+
if (
|
|
189
|
+
tasks.length === 0 ||
|
|
190
|
+
!isTaskListVisible ||
|
|
191
|
+
autoHidden ||
|
|
192
|
+
(allCompleted && !hadIncompleteRef.current)
|
|
193
|
+
) {
|
|
176
194
|
return null;
|
|
177
195
|
}
|
|
178
196
|
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -482,7 +482,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
482
482
|
|
|
483
483
|
// Inject worktree session into this agent's own DI container so system
|
|
484
484
|
// prompts and permission checks reflect the CLI -w worktree. This state is
|
|
485
|
-
// per-session, not process-global (see specs/
|
|
485
|
+
// per-session, not process-global (see docs/specs/multi-agent/worktree.md FR-042).
|
|
486
486
|
if (worktreeSession) {
|
|
487
487
|
const session: WorktreeSession = {
|
|
488
488
|
originalCwd: originalCwd ?? worktreeSession.repoRoot,
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import yargs from "yargs";
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { startCli } from "./cli.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Scope,
|
|
6
|
+
generateRandomName,
|
|
7
|
+
loadMergedWaveConfig,
|
|
8
|
+
type PermissionMode,
|
|
9
|
+
} from "wave-agent-sdk";
|
|
5
10
|
import { createWorktree, type WorktreeSession } from "./utils/worktree.js";
|
|
6
11
|
import path from "path";
|
|
7
12
|
import { readFileSync } from "fs";
|
|
@@ -320,7 +325,8 @@ export async function main() {
|
|
|
320
325
|
if (!name || name === "") {
|
|
321
326
|
name = generateRandomName();
|
|
322
327
|
}
|
|
323
|
-
|
|
328
|
+
const baseRef = loadMergedWaveConfig(originalCwd)?.worktree?.baseRef;
|
|
329
|
+
worktreeSession = await createWorktree(name, originalCwd, { baseRef });
|
|
324
330
|
|
|
325
331
|
// Note: the full worktree session (originalCwd etc.) is injected into the
|
|
326
332
|
// agent's DI container after the agent is created in useChat.tsx. This keeps
|
package/src/print-cli.ts
CHANGED
|
@@ -154,8 +154,18 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
154
154
|
process.stdout.write("\n");
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// Wait for running background tasks
|
|
158
|
-
|
|
157
|
+
// Wait for running background tasks/subagents to complete AND for the main
|
|
158
|
+
// agent to finish processing their completion notifications. The last
|
|
159
|
+
// background task flips status to "completed" before its notification is
|
|
160
|
+
// enqueued and before the main agent's follow-up turn runs, so
|
|
161
|
+
// hasRunningBackgroundWork alone becomes false while the main agent is
|
|
162
|
+
// still mid-turn — causing a TOCTOU race that aborts the final response.
|
|
163
|
+
// Including isLoading and queued notifications closes that gap.
|
|
164
|
+
while (
|
|
165
|
+
agent.hasRunningBackgroundWork ||
|
|
166
|
+
agent.isLoading ||
|
|
167
|
+
agent.hasPendingMessages
|
|
168
|
+
) {
|
|
159
169
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
160
170
|
}
|
|
161
171
|
|
|
@@ -184,7 +194,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
184
194
|
const hasCommits = hasNewCommits(cwd, baseBranch);
|
|
185
195
|
|
|
186
196
|
if (!hasChanges && !hasCommits) {
|
|
187
|
-
removeWorktree(worktreeSession);
|
|
197
|
+
await removeWorktree(worktreeSession);
|
|
188
198
|
} else {
|
|
189
199
|
process.stdout.write(
|
|
190
200
|
`\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
|
|
@@ -220,7 +230,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
220
230
|
const hasCommits = hasNewCommits(cwd, baseBranch);
|
|
221
231
|
|
|
222
232
|
if (!hasChanges && !hasCommits) {
|
|
223
|
-
removeWorktree(worktreeSession);
|
|
233
|
+
await removeWorktree(worktreeSession);
|
|
224
234
|
} else {
|
|
225
235
|
process.stdout.write(
|
|
226
236
|
`\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "wave-agent-sdk";
|
|
8
8
|
|
|
9
9
|
export interface ConfirmationState {
|
|
10
|
-
selectedOption: "
|
|
10
|
+
selectedOption: "auto" | "bypass" | "allow" | "alternative";
|
|
11
11
|
alternativeText: string;
|
|
12
12
|
alternativeCursorPosition: number;
|
|
13
13
|
hasUserInput: boolean;
|
|
@@ -99,13 +99,7 @@ export function confirmationReducer(
|
|
|
99
99
|
|
|
100
100
|
if (key.return) {
|
|
101
101
|
let decision: PermissionDecision | null = null;
|
|
102
|
-
if (state.selectedOption === "
|
|
103
|
-
decision = {
|
|
104
|
-
behavior: "allow",
|
|
105
|
-
newPermissionMode: "acceptEdits",
|
|
106
|
-
clearContext: true,
|
|
107
|
-
};
|
|
108
|
-
} else if (state.selectedOption === "allow") {
|
|
102
|
+
if (state.selectedOption === "allow") {
|
|
109
103
|
if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
|
|
110
104
|
decision = { behavior: "allow", newPermissionMode: "default" };
|
|
111
105
|
} else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
|
|
@@ -134,6 +128,11 @@ export function confirmationReducer(
|
|
|
134
128
|
} else {
|
|
135
129
|
decision = { behavior: "allow", newPermissionMode: "acceptEdits" };
|
|
136
130
|
}
|
|
131
|
+
} else if (state.selectedOption === "bypass") {
|
|
132
|
+
decision = {
|
|
133
|
+
behavior: "allow",
|
|
134
|
+
newPermissionMode: "bypassPermissions",
|
|
135
|
+
};
|
|
137
136
|
} else if (state.alternativeText.trim()) {
|
|
138
137
|
decision = {
|
|
139
138
|
behavior: "deny",
|
|
@@ -174,9 +173,10 @@ export function confirmationReducer(
|
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
const availableOptions: ConfirmationState["selectedOption"][] = [];
|
|
177
|
-
if (toolName === EXIT_PLAN_MODE_TOOL_NAME) availableOptions.push("clear");
|
|
178
176
|
availableOptions.push("allow");
|
|
179
177
|
if (!hidePersistentOption) availableOptions.push("auto");
|
|
178
|
+
if (toolName === BASH_TOOL_NAME || toolName === EXIT_PLAN_MODE_TOOL_NAME)
|
|
179
|
+
availableOptions.push("bypass");
|
|
180
180
|
availableOptions.push("alternative");
|
|
181
181
|
|
|
182
182
|
if (key.upArrow) {
|