wave-code 1.1.5 → 1.3.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/dist/bundle/wave.mjs +1075 -613
- package/package.json +4 -3
- package/src/cli.tsx +9 -0
- package/src/commands/plugin/install.ts +6 -7
- package/src/commands/plugin/uninstall.ts +11 -5
- package/src/components/ChatInterface.tsx +3 -0
- package/src/components/ConfirmationDetails.tsx +36 -15
- package/src/components/ConfirmationSelector.tsx +39 -0
- package/src/components/HooksManager.tsx +286 -0
- package/src/components/InputBox.tsx +125 -51
- package/src/components/Markdown.tsx +21 -8
- package/src/components/MarketplaceDetail.tsx +10 -27
- package/src/components/MarketplaceView.tsx +17 -3
- package/src/components/MessageBlockItem.tsx +0 -5
- package/src/components/MessageList.tsx +0 -1
- package/src/components/Notifications.tsx +7 -0
- package/src/components/PlanView.tsx +146 -0
- package/src/components/PluginManagerShell.tsx +3 -3
- package/src/components/PluginManagerTypes.ts +2 -1
- package/src/components/ResumeCommand.tsx +84 -0
- package/src/components/ScrolledContent.tsx +71 -0
- package/src/components/SessionSelector.tsx +184 -6
- package/src/components/SessionSelectorFlow.tsx +246 -0
- package/src/constants/commands.ts +30 -0
- package/src/contexts/useChat.tsx +252 -98
- package/src/daemon/commands.ts +1046 -67
- package/src/daemon-cli.ts +6 -0
- package/src/hooks/useInputManager.ts +30 -0
- package/src/hooks/useLineScroll.ts +58 -0
- package/src/hooks/usePluginManager.ts +104 -78
- package/src/index.ts +156 -16
- package/src/managers/inputHandlers.ts +16 -9
- package/src/managers/inputReducer.ts +39 -9
- package/src/reducers/confirmationReducer.ts +20 -1
- package/src/reducers/hooksManagerReducer.ts +92 -0
- package/src/reducers/selectorReducer.ts +3 -0
- package/src/session-selector-cli.tsx +9 -255
- package/src/stdio/agentBridge.ts +932 -133
- package/src/stdio/daemonServer.ts +26 -82
- package/src/stdio/protocol.ts +23 -4
- package/src/stdio-cli.ts +60 -0
- package/src/utils/rewindCheckpoints.ts +2 -2
- package/src/utils/sessionOwnership.ts +62 -0
- package/src/utils/worktree.ts +175 -50
- package/src/components/BangDisplay.tsx +0 -41
- package/src/components/PlanDisplay.tsx +0 -18
package/src/index.ts
CHANGED
|
@@ -188,7 +188,7 @@ export async function main() {
|
|
|
188
188
|
})
|
|
189
189
|
.option("scope", {
|
|
190
190
|
alias: "s",
|
|
191
|
-
describe: "Scope to enable the plugin in",
|
|
191
|
+
describe: "Scope to enable the plugin in (default: user)",
|
|
192
192
|
choices: ["user", "project", "local"],
|
|
193
193
|
type: "string",
|
|
194
194
|
});
|
|
@@ -218,18 +218,30 @@ export async function main() {
|
|
|
218
218
|
)
|
|
219
219
|
.command(
|
|
220
220
|
"uninstall <plugin>",
|
|
221
|
-
"Uninstall a plugin",
|
|
221
|
+
"Uninstall a plugin from one scope (default: the scope it is enabled in for the current directory)",
|
|
222
222
|
(yargs) => {
|
|
223
|
-
return yargs
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
return yargs
|
|
224
|
+
.positional("plugin", {
|
|
225
|
+
describe: "Plugin to uninstall (format: name@marketplace)",
|
|
226
|
+
type: "string",
|
|
227
|
+
})
|
|
228
|
+
.option("scope", {
|
|
229
|
+
alias: "s",
|
|
230
|
+
describe: "Scope to uninstall from",
|
|
231
|
+
choices: ["user", "project", "local"],
|
|
232
|
+
type: "string",
|
|
233
|
+
});
|
|
227
234
|
},
|
|
228
235
|
async (argv) => {
|
|
229
236
|
const { uninstallPluginCommand } = await import(
|
|
230
237
|
"./commands/plugin/uninstall.js"
|
|
231
238
|
);
|
|
232
|
-
await uninstallPluginCommand(
|
|
239
|
+
await uninstallPluginCommand(
|
|
240
|
+
argv as {
|
|
241
|
+
plugin: string;
|
|
242
|
+
scope?: Scope;
|
|
243
|
+
},
|
|
244
|
+
);
|
|
233
245
|
},
|
|
234
246
|
)
|
|
235
247
|
.command(
|
|
@@ -251,10 +263,47 @@ export async function main() {
|
|
|
251
263
|
})
|
|
252
264
|
.command(
|
|
253
265
|
"daemon",
|
|
254
|
-
"Manage the wave daemon (client subcommands —
|
|
266
|
+
"Manage the wave daemon (client subcommands — daemons start on demand, or directly via `wave --daemon <socket>`)",
|
|
255
267
|
(yargs) => {
|
|
256
268
|
return yargs
|
|
257
269
|
.help()
|
|
270
|
+
.command(
|
|
271
|
+
"create",
|
|
272
|
+
"Create a new session hosted by the daemon and print its sessionId",
|
|
273
|
+
(yargs) => {
|
|
274
|
+
return yargs
|
|
275
|
+
.option("workdir", {
|
|
276
|
+
describe:
|
|
277
|
+
"Working directory for the new session (default: current directory)",
|
|
278
|
+
type: "string",
|
|
279
|
+
})
|
|
280
|
+
.option("permission-mode", {
|
|
281
|
+
describe:
|
|
282
|
+
"Permission mode for the new session (default: bypassPermissions; options: default, bypassPermissions, acceptEdits, plan, dontAsk)",
|
|
283
|
+
type: "string",
|
|
284
|
+
})
|
|
285
|
+
.option("model", {
|
|
286
|
+
describe:
|
|
287
|
+
"Model override for the new session (default: configured model)",
|
|
288
|
+
type: "string",
|
|
289
|
+
})
|
|
290
|
+
.option("worktree", {
|
|
291
|
+
describe:
|
|
292
|
+
"Create the session in a new git worktree (name optional — auto-generated when omitted)",
|
|
293
|
+
type: "string",
|
|
294
|
+
});
|
|
295
|
+
},
|
|
296
|
+
async (argv) => {
|
|
297
|
+
const { daemonCreateCommand, DEFAULT_DAEMON_SOCKET } =
|
|
298
|
+
await import("./daemon/commands.js");
|
|
299
|
+
await daemonCreateCommand(DEFAULT_DAEMON_SOCKET, {
|
|
300
|
+
workdir: argv.workdir as string | undefined,
|
|
301
|
+
permissionMode: argv.permissionMode as string | undefined,
|
|
302
|
+
model: argv.model as string | undefined,
|
|
303
|
+
worktree: argv.worktree as string | undefined,
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
)
|
|
258
307
|
.command(
|
|
259
308
|
"list",
|
|
260
309
|
"List sessions hosted by the daemon (in-memory registry)",
|
|
@@ -275,8 +324,9 @@ export async function main() {
|
|
|
275
324
|
type: "string",
|
|
276
325
|
})
|
|
277
326
|
.option("lines", {
|
|
278
|
-
describe:
|
|
279
|
-
|
|
327
|
+
describe:
|
|
328
|
+
"Number of recent messages to show (0 = status line only)",
|
|
329
|
+
default: 1,
|
|
280
330
|
type: "number",
|
|
281
331
|
});
|
|
282
332
|
},
|
|
@@ -290,9 +340,50 @@ export async function main() {
|
|
|
290
340
|
);
|
|
291
341
|
},
|
|
292
342
|
)
|
|
343
|
+
.command(
|
|
344
|
+
"wait <sessionId>",
|
|
345
|
+
"Block until a session goes idle (or waits for approval), then print its final snapshot (exit 0 = idle, 3 = waiting for approval, 1 = error/timeout). Idle = no turn running, no background task/subagent running, nothing queued (same as `wave -p`); a session holding a long-running background task never goes idle, so give it --timeout",
|
|
346
|
+
(yargs) => {
|
|
347
|
+
return yargs
|
|
348
|
+
.positional("sessionId", {
|
|
349
|
+
describe: "Session ID hosted by the daemon",
|
|
350
|
+
type: "string",
|
|
351
|
+
})
|
|
352
|
+
.option("lines", {
|
|
353
|
+
describe:
|
|
354
|
+
"Number of recent messages to print on exit (0 = status line only)",
|
|
355
|
+
default: 1,
|
|
356
|
+
type: "number",
|
|
357
|
+
})
|
|
358
|
+
.option("from-busy", {
|
|
359
|
+
describe:
|
|
360
|
+
"Wait until the session has been observed busy at least once before accepting idle (removes the stale-snapshot race right after an async `send`; a running background task also counts as busy)",
|
|
361
|
+
default: false,
|
|
362
|
+
type: "boolean",
|
|
363
|
+
})
|
|
364
|
+
.option("timeout", {
|
|
365
|
+
describe:
|
|
366
|
+
"Seconds to wait before giving up (default: wait forever) — required for sessions with a long-running background task, which never go idle",
|
|
367
|
+
type: "number",
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
async (argv) => {
|
|
371
|
+
const { daemonWaitCommand, DEFAULT_DAEMON_SOCKET } =
|
|
372
|
+
await import("./daemon/commands.js");
|
|
373
|
+
await daemonWaitCommand(
|
|
374
|
+
DEFAULT_DAEMON_SOCKET,
|
|
375
|
+
argv.sessionId as string,
|
|
376
|
+
{
|
|
377
|
+
lines: argv.lines as number,
|
|
378
|
+
fromBusy: argv.fromBusy as boolean,
|
|
379
|
+
timeout: argv.timeout as number | undefined,
|
|
380
|
+
},
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
)
|
|
293
384
|
.command(
|
|
294
385
|
"send <sessionId> <message>",
|
|
295
|
-
"Inject a message into a session
|
|
386
|
+
"Inject a message into a session (async by default; --wait N to get the reply)",
|
|
296
387
|
(yargs) => {
|
|
297
388
|
return yargs
|
|
298
389
|
.positional("sessionId", {
|
|
@@ -303,9 +394,10 @@ export async function main() {
|
|
|
303
394
|
describe: "Message to send",
|
|
304
395
|
type: "string",
|
|
305
396
|
})
|
|
306
|
-
.option("
|
|
307
|
-
describe:
|
|
308
|
-
|
|
397
|
+
.option("wait", {
|
|
398
|
+
describe:
|
|
399
|
+
"Seconds to wait for the reply and print it (0 = fire-and-forget, the default)",
|
|
400
|
+
default: 0,
|
|
309
401
|
type: "number",
|
|
310
402
|
});
|
|
311
403
|
},
|
|
@@ -316,7 +408,7 @@ export async function main() {
|
|
|
316
408
|
DEFAULT_DAEMON_SOCKET,
|
|
317
409
|
argv.sessionId as string,
|
|
318
410
|
argv.message as string,
|
|
319
|
-
{
|
|
411
|
+
{ wait: argv.wait as number },
|
|
320
412
|
);
|
|
321
413
|
},
|
|
322
414
|
)
|
|
@@ -346,7 +438,8 @@ export async function main() {
|
|
|
346
438
|
type: "string",
|
|
347
439
|
})
|
|
348
440
|
.option("answer", {
|
|
349
|
-
describe:
|
|
441
|
+
describe:
|
|
442
|
+
'AskUserQuestion answers: JSON object of {question: answer} or option numbers per question (e.g. "0" or "1,0")',
|
|
350
443
|
type: "string",
|
|
351
444
|
})
|
|
352
445
|
.option("rule", {
|
|
@@ -394,6 +487,53 @@ export async function main() {
|
|
|
394
487
|
);
|
|
395
488
|
},
|
|
396
489
|
)
|
|
490
|
+
.command(
|
|
491
|
+
"destroy <sessionId>",
|
|
492
|
+
"Destroy a daemon session (idempotent)",
|
|
493
|
+
(yargs) => {
|
|
494
|
+
return yargs
|
|
495
|
+
.positional("sessionId", {
|
|
496
|
+
describe: "Session ID of the session to destroy",
|
|
497
|
+
type: "string",
|
|
498
|
+
})
|
|
499
|
+
.option("remove-worktree", {
|
|
500
|
+
describe:
|
|
501
|
+
"Also remove the session's git worktree (resolved via git, removed via protocol removeWorktree) before destroying",
|
|
502
|
+
type: "boolean",
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
async (argv) => {
|
|
506
|
+
const { daemonDestroyCommand, DEFAULT_DAEMON_SOCKET } =
|
|
507
|
+
await import("./daemon/commands.js");
|
|
508
|
+
await daemonDestroyCommand(
|
|
509
|
+
DEFAULT_DAEMON_SOCKET,
|
|
510
|
+
argv.sessionId as string,
|
|
511
|
+
{
|
|
512
|
+
removeWorktree: argv.removeWorktree as boolean | undefined,
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
},
|
|
516
|
+
)
|
|
517
|
+
.command(
|
|
518
|
+
"stop",
|
|
519
|
+
"Gracefully stop the daemon (idempotent — no-op when it is not running)",
|
|
520
|
+
{},
|
|
521
|
+
async () => {
|
|
522
|
+
const { daemonStopCommand, DEFAULT_DAEMON_SOCKET } =
|
|
523
|
+
await import("./daemon/commands.js");
|
|
524
|
+
await daemonStopCommand(DEFAULT_DAEMON_SOCKET);
|
|
525
|
+
},
|
|
526
|
+
)
|
|
527
|
+
.command(
|
|
528
|
+
"restart",
|
|
529
|
+
"Restart the daemon (graceful stop + relaunch; starts one when not running)",
|
|
530
|
+
{},
|
|
531
|
+
async () => {
|
|
532
|
+
const { daemonRestartCommand, DEFAULT_DAEMON_SOCKET } =
|
|
533
|
+
await import("./daemon/commands.js");
|
|
534
|
+
await daemonRestartCommand(DEFAULT_DAEMON_SOCKET);
|
|
535
|
+
},
|
|
536
|
+
)
|
|
397
537
|
.demandCommand(1, "Please specify a daemon subcommand");
|
|
398
538
|
},
|
|
399
539
|
)
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
InputAction,
|
|
7
7
|
InputManagerCallbacks,
|
|
8
8
|
isPasteImageKey,
|
|
9
|
+
permissionModeCycle,
|
|
9
10
|
} from "./inputReducer.js";
|
|
10
11
|
|
|
11
12
|
export const expandLongTextPlaceholders = (
|
|
@@ -136,15 +137,10 @@ export const cyclePermissionMode = (
|
|
|
136
137
|
dispatch: React.Dispatch<InputAction>,
|
|
137
138
|
callbacks: Partial<InputManagerCallbacks>,
|
|
138
139
|
) => {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"plan",
|
|
144
|
-
];
|
|
145
|
-
const currentIndex = modes.indexOf(currentMode);
|
|
146
|
-
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
|
|
147
|
-
const nextMode = modes[nextIndex];
|
|
140
|
+
const currentIndex = permissionModeCycle.indexOf(currentMode);
|
|
141
|
+
const nextIndex =
|
|
142
|
+
currentIndex === -1 ? 0 : (currentIndex + 1) % permissionModeCycle.length;
|
|
143
|
+
const nextMode = permissionModeCycle[nextIndex];
|
|
148
144
|
|
|
149
145
|
callbacks.logger?.debug("Cycling permission mode", {
|
|
150
146
|
from: currentMode,
|
|
@@ -372,8 +368,12 @@ export const handleCommandSelect = (
|
|
|
372
368
|
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
|
|
373
369
|
} else if (command === "skills") {
|
|
374
370
|
dispatch({ type: "SET_SHOW_SKILLS_MANAGER", payload: true });
|
|
371
|
+
} else if (command === "hooks") {
|
|
372
|
+
dispatch({ type: "SET_SHOW_HOOKS_MANAGER", payload: true });
|
|
375
373
|
} else if (command === "rewind") {
|
|
376
374
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
375
|
+
} else if (command === "resume") {
|
|
376
|
+
dispatch({ type: "SET_SHOW_RESUME_SELECTOR", payload: true });
|
|
377
377
|
} else if (command === "help") {
|
|
378
378
|
dispatch({ type: "SET_SHOW_HELP", payload: true });
|
|
379
379
|
} else if (command === "status") {
|
|
@@ -399,8 +399,12 @@ export const handleCommandSelect = (
|
|
|
399
399
|
await callbacks.onClearMessages?.();
|
|
400
400
|
} else if (command === "compact") {
|
|
401
401
|
await callbacks.onCompact?.();
|
|
402
|
+
} else if (command === "reload-plugins") {
|
|
403
|
+
await callbacks.onReloadPlugins?.();
|
|
402
404
|
} else if (command === "add-dir") {
|
|
403
405
|
await callbacks.onAddDir?.();
|
|
406
|
+
} else if (command === "rename") {
|
|
407
|
+
await callbacks.onRenameSession?.();
|
|
404
408
|
}
|
|
405
409
|
}
|
|
406
410
|
})();
|
|
@@ -754,6 +758,7 @@ export const handleInput = async (
|
|
|
754
758
|
state.showBackgroundTaskManager ||
|
|
755
759
|
state.showMcpManager ||
|
|
756
760
|
state.showRewindManager ||
|
|
761
|
+
state.showResumeSelector ||
|
|
757
762
|
state.showHelp ||
|
|
758
763
|
state.showStatusCommand ||
|
|
759
764
|
state.showPluginManager ||
|
|
@@ -778,6 +783,7 @@ export const handleInput = async (
|
|
|
778
783
|
state.showBackgroundTaskManager ||
|
|
779
784
|
state.showMcpManager ||
|
|
780
785
|
state.showRewindManager ||
|
|
786
|
+
state.showResumeSelector ||
|
|
781
787
|
state.showHelp ||
|
|
782
788
|
state.showStatusCommand ||
|
|
783
789
|
state.showPluginManager ||
|
|
@@ -788,6 +794,7 @@ export const handleInput = async (
|
|
|
788
794
|
state.showBackgroundTaskManager ||
|
|
789
795
|
state.showMcpManager ||
|
|
790
796
|
state.showRewindManager ||
|
|
797
|
+
state.showResumeSelector ||
|
|
791
798
|
state.showHelp ||
|
|
792
799
|
state.showStatusCommand ||
|
|
793
800
|
state.showPluginManager ||
|
|
@@ -37,6 +37,18 @@ export const btwOverlayActiveRef: { current: boolean } = { current: false };
|
|
|
37
37
|
|
|
38
38
|
export const ESC_DOUBLE_PRESS_TIMEOUT_MS = 1000;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Shift+Tab 循环的权限模式顺序(对齐 Claude Code:plan 之后才进入
|
|
42
|
+
* bypassPermissions)。单独导出,避免 handler 与 reducer 两条路径各写一份
|
|
43
|
+
* 而漂移。
|
|
44
|
+
*/
|
|
45
|
+
export const permissionModeCycle: PermissionMode[] = [
|
|
46
|
+
"default",
|
|
47
|
+
"acceptEdits",
|
|
48
|
+
"plan",
|
|
49
|
+
"bypassPermissions",
|
|
50
|
+
];
|
|
51
|
+
|
|
40
52
|
/**
|
|
41
53
|
* Paste-image shortcut: Ctrl+V everywhere, plus Alt+V on Windows — Windows
|
|
42
54
|
* terminals reserve Ctrl+V for their own system paste, so the key never
|
|
@@ -108,7 +120,10 @@ export interface InputManagerCallbacks {
|
|
|
108
120
|
) => Promise<string>;
|
|
109
121
|
onClearMessages?: () => Promise<void>;
|
|
110
122
|
onCompact?: (instructions?: string) => Promise<void>;
|
|
123
|
+
onReloadPlugins?: () => Promise<void>;
|
|
111
124
|
onAddDir?: (args?: string) => Promise<void>;
|
|
125
|
+
onRenameSession?: (args?: string) => Promise<void>;
|
|
126
|
+
onPlanCommand?: (args?: string) => Promise<void> | void;
|
|
112
127
|
sessionId?: string;
|
|
113
128
|
workdir?: string;
|
|
114
129
|
getFullMessageThread?: () => Promise<{
|
|
@@ -141,6 +156,7 @@ export interface InputState {
|
|
|
141
156
|
showMcpManager: boolean;
|
|
142
157
|
showAgentsManager: boolean;
|
|
143
158
|
showRewindManager: boolean;
|
|
159
|
+
showResumeSelector: boolean;
|
|
144
160
|
showHelp: boolean;
|
|
145
161
|
showStatusCommand: boolean;
|
|
146
162
|
showLoginCommand: boolean;
|
|
@@ -148,6 +164,7 @@ export interface InputState {
|
|
|
148
164
|
showModelSelector: boolean;
|
|
149
165
|
showWorkflowManager: boolean;
|
|
150
166
|
showSkillsManager: boolean;
|
|
167
|
+
showHooksManager: boolean;
|
|
151
168
|
permissionMode: PermissionMode;
|
|
152
169
|
selectorJustUsed: boolean;
|
|
153
170
|
history: PromptEntry[];
|
|
@@ -180,6 +197,7 @@ export const initialState: InputState = {
|
|
|
180
197
|
showMcpManager: false,
|
|
181
198
|
showAgentsManager: false,
|
|
182
199
|
showRewindManager: false,
|
|
200
|
+
showResumeSelector: false,
|
|
183
201
|
showHelp: false,
|
|
184
202
|
showStatusCommand: false,
|
|
185
203
|
showLoginCommand: false,
|
|
@@ -187,6 +205,7 @@ export const initialState: InputState = {
|
|
|
187
205
|
showModelSelector: false,
|
|
188
206
|
showWorkflowManager: false,
|
|
189
207
|
showSkillsManager: false,
|
|
208
|
+
showHooksManager: false,
|
|
190
209
|
permissionMode: "default",
|
|
191
210
|
selectorJustUsed: false,
|
|
192
211
|
history: [],
|
|
@@ -462,6 +481,7 @@ export type InputAction =
|
|
|
462
481
|
| { type: "SET_SHOW_MCP_MANAGER"; payload: boolean }
|
|
463
482
|
| { type: "SET_SHOW_AGENTS_MANAGER"; payload: boolean }
|
|
464
483
|
| { type: "SET_SHOW_REWIND_MANAGER"; payload: boolean }
|
|
484
|
+
| { type: "SET_SHOW_RESUME_SELECTOR"; payload: boolean }
|
|
465
485
|
| { type: "SET_SHOW_HELP"; payload: boolean }
|
|
466
486
|
| { type: "SET_SHOW_STATUS_COMMAND"; payload: boolean }
|
|
467
487
|
| { type: "SET_SHOW_LOGIN_COMMAND"; payload: boolean }
|
|
@@ -469,6 +489,7 @@ export type InputAction =
|
|
|
469
489
|
| { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
|
|
470
490
|
| { type: "SET_SHOW_WORKFLOW_MANAGER"; payload: boolean }
|
|
471
491
|
| { type: "SET_SHOW_SKILLS_MANAGER"; payload: boolean }
|
|
492
|
+
| { type: "SET_SHOW_HOOKS_MANAGER"; payload: boolean }
|
|
472
493
|
| { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
|
|
473
494
|
| { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
|
|
474
495
|
| { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
|
|
@@ -663,6 +684,12 @@ export function inputReducer(
|
|
|
663
684
|
showRewindManager: action.payload,
|
|
664
685
|
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
665
686
|
};
|
|
687
|
+
case "SET_SHOW_RESUME_SELECTOR":
|
|
688
|
+
return {
|
|
689
|
+
...state,
|
|
690
|
+
showResumeSelector: action.payload,
|
|
691
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
692
|
+
};
|
|
666
693
|
case "SET_SHOW_HELP":
|
|
667
694
|
return {
|
|
668
695
|
...state,
|
|
@@ -705,6 +732,12 @@ export function inputReducer(
|
|
|
705
732
|
showSkillsManager: action.payload,
|
|
706
733
|
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
707
734
|
};
|
|
735
|
+
case "SET_SHOW_HOOKS_MANAGER":
|
|
736
|
+
return {
|
|
737
|
+
...state,
|
|
738
|
+
showHooksManager: action.payload,
|
|
739
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
740
|
+
};
|
|
708
741
|
case "SET_PERMISSION_MODE":
|
|
709
742
|
return { ...state, permissionMode: action.payload };
|
|
710
743
|
case "SET_SELECTOR_JUST_USED":
|
|
@@ -1066,6 +1099,7 @@ export function inputReducer(
|
|
|
1066
1099
|
state.showMcpManager ||
|
|
1067
1100
|
state.showAgentsManager ||
|
|
1068
1101
|
state.showRewindManager ||
|
|
1102
|
+
state.showResumeSelector ||
|
|
1069
1103
|
state.showHelp ||
|
|
1070
1104
|
state.showStatusCommand ||
|
|
1071
1105
|
state.showLoginCommand ||
|
|
@@ -1113,16 +1147,12 @@ export function inputReducer(
|
|
|
1113
1147
|
|
|
1114
1148
|
// 3. Special Shortcuts
|
|
1115
1149
|
if (key.tab && key.shift) {
|
|
1116
|
-
const
|
|
1117
|
-
"default",
|
|
1118
|
-
"acceptEdits",
|
|
1119
|
-
"bypassPermissions",
|
|
1120
|
-
"plan",
|
|
1121
|
-
];
|
|
1122
|
-
const currentIndex = modes.indexOf(state.permissionMode);
|
|
1150
|
+
const currentIndex = permissionModeCycle.indexOf(state.permissionMode);
|
|
1123
1151
|
const nextIndex =
|
|
1124
|
-
currentIndex === -1
|
|
1125
|
-
|
|
1152
|
+
currentIndex === -1
|
|
1153
|
+
? 0
|
|
1154
|
+
: (currentIndex + 1) % permissionModeCycle.length;
|
|
1155
|
+
const nextMode = permissionModeCycle[nextIndex];
|
|
1126
1156
|
return {
|
|
1127
1157
|
...state,
|
|
1128
1158
|
permissionMode: nextMode,
|
|
@@ -2,12 +2,14 @@ import { Key } from "ink";
|
|
|
2
2
|
import type { PermissionDecision, PermissionMode } from "wave-agent-sdk";
|
|
3
3
|
import {
|
|
4
4
|
BASH_TOOL_NAME,
|
|
5
|
+
EDIT_TOOL_NAME,
|
|
5
6
|
EXIT_PLAN_MODE_TOOL_NAME,
|
|
6
7
|
ENTER_PLAN_MODE_TOOL_NAME,
|
|
8
|
+
WRITE_TOOL_NAME,
|
|
7
9
|
} from "wave-agent-sdk";
|
|
8
10
|
|
|
9
11
|
export interface ConfirmationState {
|
|
10
|
-
selectedOption: "auto" | "bypass" | "allow" | "alternative";
|
|
12
|
+
selectedOption: "auto" | "bypass" | "allow" | "addDirectory" | "alternative";
|
|
11
13
|
alternativeText: string;
|
|
12
14
|
alternativeCursorPosition: number;
|
|
13
15
|
hasUserInput: boolean;
|
|
@@ -30,6 +32,7 @@ export type ConfirmationAction =
|
|
|
30
32
|
toolInput?: Record<string, unknown>;
|
|
31
33
|
suggestedPrefix?: string;
|
|
32
34
|
hidePersistentOption?: boolean;
|
|
35
|
+
outsideSafeZoneDirectory?: string;
|
|
33
36
|
permissionMode?: PermissionMode;
|
|
34
37
|
};
|
|
35
38
|
|
|
@@ -96,6 +99,7 @@ export function confirmationReducer(
|
|
|
96
99
|
toolInput,
|
|
97
100
|
suggestedPrefix,
|
|
98
101
|
hidePersistentOption,
|
|
102
|
+
outsideSafeZoneDirectory,
|
|
99
103
|
permissionMode,
|
|
100
104
|
} = action;
|
|
101
105
|
|
|
@@ -135,6 +139,16 @@ export function confirmationReducer(
|
|
|
135
139
|
behavior: "allow",
|
|
136
140
|
newPermissionMode: "bypassPermissions",
|
|
137
141
|
};
|
|
142
|
+
} else if (
|
|
143
|
+
state.selectedOption === "addDirectory" &&
|
|
144
|
+
outsideSafeZoneDirectory
|
|
145
|
+
) {
|
|
146
|
+
// Approve this operation AND add the out-of-Safe-Zone target
|
|
147
|
+
// directory to this session's Safe Zone (in-memory only).
|
|
148
|
+
decision = {
|
|
149
|
+
behavior: "allow",
|
|
150
|
+
newAdditionalDirectory: outsideSafeZoneDirectory,
|
|
151
|
+
};
|
|
138
152
|
} else if (state.alternativeText.trim()) {
|
|
139
153
|
decision = {
|
|
140
154
|
behavior: "deny",
|
|
@@ -176,6 +190,11 @@ export function confirmationReducer(
|
|
|
176
190
|
|
|
177
191
|
const availableOptions: ConfirmationState["selectedOption"][] = [];
|
|
178
192
|
availableOptions.push("allow");
|
|
193
|
+
if (
|
|
194
|
+
(toolName === EDIT_TOOL_NAME || toolName === WRITE_TOOL_NAME) &&
|
|
195
|
+
outsideSafeZoneDirectory
|
|
196
|
+
)
|
|
197
|
+
availableOptions.push("addDirectory");
|
|
179
198
|
if (!hidePersistentOption) availableOptions.push("auto");
|
|
180
199
|
if (
|
|
181
200
|
toolName === EXIT_PLAN_MODE_TOOL_NAME ||
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Key } from "ink";
|
|
2
|
+
|
|
3
|
+
export type PendingEffect = { type: "CANCEL" };
|
|
4
|
+
|
|
5
|
+
export interface HooksManagerState {
|
|
6
|
+
selectedIndex: number;
|
|
7
|
+
viewMode: "list" | "detail";
|
|
8
|
+
pendingEffect: PendingEffect | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type HooksManagerAction =
|
|
12
|
+
| { type: "MOVE_UP" }
|
|
13
|
+
| { type: "MOVE_DOWN"; itemCount: number }
|
|
14
|
+
| { type: "SET_VIEW_MODE"; viewMode: "list" | "detail" }
|
|
15
|
+
| {
|
|
16
|
+
type: "HANDLE_KEY";
|
|
17
|
+
input: string;
|
|
18
|
+
key: Key;
|
|
19
|
+
itemCount: number;
|
|
20
|
+
}
|
|
21
|
+
| { type: "CLEAR_PENDING_EFFECT" };
|
|
22
|
+
|
|
23
|
+
export function hooksManagerReducer(
|
|
24
|
+
state: HooksManagerState,
|
|
25
|
+
action: HooksManagerAction,
|
|
26
|
+
): HooksManagerState {
|
|
27
|
+
switch (action.type) {
|
|
28
|
+
case "MOVE_UP":
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
32
|
+
};
|
|
33
|
+
case "MOVE_DOWN":
|
|
34
|
+
return {
|
|
35
|
+
...state,
|
|
36
|
+
selectedIndex: Math.min(
|
|
37
|
+
Math.max(0, action.itemCount - 1),
|
|
38
|
+
state.selectedIndex + 1,
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
case "SET_VIEW_MODE":
|
|
42
|
+
return { ...state, viewMode: action.viewMode };
|
|
43
|
+
case "HANDLE_KEY": {
|
|
44
|
+
const { key, itemCount } = action;
|
|
45
|
+
|
|
46
|
+
if (key.return) {
|
|
47
|
+
if (state.viewMode === "list") {
|
|
48
|
+
return { ...state, viewMode: "detail" };
|
|
49
|
+
}
|
|
50
|
+
// Aligned with AgentsManager/SkillsManager detail view: Enter
|
|
51
|
+
// returns to the list.
|
|
52
|
+
return { ...state, viewMode: "list" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (key.escape) {
|
|
56
|
+
if (state.viewMode === "detail") {
|
|
57
|
+
return { ...state, viewMode: "list" };
|
|
58
|
+
}
|
|
59
|
+
return { ...state, pendingEffect: { type: "CANCEL" } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Detail view does not respond to arrow keys (aligned with
|
|
63
|
+
// AgentsManager/SkillsManager).
|
|
64
|
+
if (state.viewMode === "detail") {
|
|
65
|
+
return state;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (key.upArrow) {
|
|
69
|
+
return {
|
|
70
|
+
...state,
|
|
71
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (key.downArrow) {
|
|
76
|
+
return {
|
|
77
|
+
...state,
|
|
78
|
+
selectedIndex: Math.min(
|
|
79
|
+
Math.max(0, itemCount - 1),
|
|
80
|
+
state.selectedIndex + 1,
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
case "CLEAR_PENDING_EFFECT":
|
|
88
|
+
return { ...state, pendingEffect: null };
|
|
89
|
+
default:
|
|
90
|
+
return state;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -15,6 +15,7 @@ export type SelectorAction<T = unknown> =
|
|
|
15
15
|
| { type: "MOVE_DOWN" }
|
|
16
16
|
| { type: "RESET_INDEX" }
|
|
17
17
|
| { type: "SET_ITEMS"; items: T[] }
|
|
18
|
+
| { type: "SET_INDEX"; index: number }
|
|
18
19
|
| { type: "HANDLE_KEY"; key: Key; hasInsert: boolean }
|
|
19
20
|
| { type: "TOGGLE_ALL_PROJECTS" }
|
|
20
21
|
| { type: "TOGGLE_ALL_WORKTREES" }
|
|
@@ -39,6 +40,8 @@ export function selectorReducer<T = unknown>(
|
|
|
39
40
|
return { ...state, selectedIndex: 0 };
|
|
40
41
|
case "SET_ITEMS":
|
|
41
42
|
return { ...state, items: action.items, selectedIndex: 0 };
|
|
43
|
+
case "SET_INDEX":
|
|
44
|
+
return { ...state, selectedIndex: action.index };
|
|
42
45
|
case "TOGGLE_ALL_PROJECTS":
|
|
43
46
|
return { ...state, showAllProjects: !state.showAllProjects };
|
|
44
47
|
case "TOGGLE_ALL_WORKTREES":
|