wave-code 0.18.3 → 0.18.5

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 (51) hide show
  1. package/dist/acp/agent.d.ts.map +1 -1
  2. package/dist/acp/agent.js +13 -0
  3. package/dist/components/CommandSelector.d.ts.map +1 -1
  4. package/dist/components/CommandSelector.js +9 -13
  5. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  6. package/dist/components/ConfirmationSelector.js +0 -1
  7. package/dist/components/DiscoverView.d.ts.map +1 -1
  8. package/dist/components/DiscoverView.js +10 -6
  9. package/dist/components/FileSelector.js +8 -8
  10. package/dist/components/HistorySearch.d.ts.map +1 -1
  11. package/dist/components/HistorySearch.js +6 -13
  12. package/dist/components/InstalledView.d.ts.map +1 -1
  13. package/dist/components/InstalledView.js +10 -6
  14. package/dist/components/LoginCommand.d.ts.map +1 -1
  15. package/dist/components/LoginCommand.js +14 -11
  16. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  17. package/dist/components/MarketplaceDetail.js +11 -7
  18. package/dist/components/MarketplaceView.d.ts.map +1 -1
  19. package/dist/components/MarketplaceView.js +10 -6
  20. package/dist/components/ModelSelector.d.ts.map +1 -1
  21. package/dist/components/ModelSelector.js +9 -15
  22. package/dist/components/SessionSelector.d.ts +5 -3
  23. package/dist/components/SessionSelector.d.ts.map +1 -1
  24. package/dist/components/SessionSelector.js +11 -7
  25. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
  26. package/dist/components/WorktreeExitPrompt.js +3 -3
  27. package/dist/reducers/questionReducer.d.ts +0 -7
  28. package/dist/reducers/questionReducer.d.ts.map +1 -1
  29. package/dist/reducers/questionReducer.js +2 -1
  30. package/dist/reducers/selectorReducer.d.ts +7 -5
  31. package/dist/reducers/selectorReducer.d.ts.map +1 -1
  32. package/dist/reducers/selectorReducer.js +4 -2
  33. package/dist/utils/worktree.d.ts.map +1 -1
  34. package/dist/utils/worktree.js +10 -10
  35. package/package.json +2 -2
  36. package/src/acp/agent.ts +14 -0
  37. package/src/components/CommandSelector.tsx +11 -15
  38. package/src/components/ConfirmationSelector.tsx +0 -1
  39. package/src/components/DiscoverView.tsx +22 -7
  40. package/src/components/FileSelector.tsx +9 -9
  41. package/src/components/HistorySearch.tsx +7 -14
  42. package/src/components/InstalledView.tsx +23 -9
  43. package/src/components/LoginCommand.tsx +33 -36
  44. package/src/components/MarketplaceDetail.tsx +25 -10
  45. package/src/components/MarketplaceView.tsx +16 -7
  46. package/src/components/ModelSelector.tsx +13 -16
  47. package/src/components/SessionSelector.tsx +19 -9
  48. package/src/components/WorktreeExitPrompt.tsx +7 -4
  49. package/src/reducers/questionReducer.ts +2 -6
  50. package/src/reducers/selectorReducer.ts +20 -10
  51. package/src/utils/worktree.ts +29 -19
@@ -1 +1 @@
1
- {"version":3,"file":"questionReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/questionReducer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"questionReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/questionReducer.ts"],"names":[],"mappings":"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"}
@@ -192,7 +192,8 @@ export function questionReducer(state, action) {
192
192
  case "CLEAR_DECISION":
193
193
  return { ...state, decision: null };
194
194
  case "HANDLE_KEY": {
195
- const { input, key, currentQuestion, questions } = action;
195
+ const { input, key, questions } = action;
196
+ const currentQuestion = questions[state.currentQuestionIndex];
196
197
  if (!currentQuestion)
197
198
  return state;
198
199
  const options = [...currentQuestion.options, { label: "Other" }];
@@ -1,22 +1,24 @@
1
1
  import { Key } from "ink";
2
- export interface SelectorState {
2
+ export interface SelectorState<T = unknown> {
3
3
  selectedIndex: number;
4
4
  pendingDecision: "select" | "insert" | "cancel" | null;
5
+ items: T[];
5
6
  }
6
- export type SelectorAction = {
7
+ export type SelectorAction<T = unknown> = {
7
8
  type: "MOVE_UP";
8
9
  } | {
9
10
  type: "MOVE_DOWN";
10
- maxIndex: number;
11
11
  } | {
12
12
  type: "RESET_INDEX";
13
+ } | {
14
+ type: "SET_ITEMS";
15
+ items: T[];
13
16
  } | {
14
17
  type: "HANDLE_KEY";
15
18
  key: Key;
16
- maxIndex: number;
17
19
  hasInsert: boolean;
18
20
  } | {
19
21
  type: "CLEAR_DECISION";
20
22
  };
21
- export declare function selectorReducer(state: SelectorState, action: SelectorAction): SelectorState;
23
+ export declare function selectorReducer<T = unknown>(state: SelectorState<T>, action: SelectorAction<T>): SelectorState<T>;
22
24
  //# sourceMappingURL=selectorReducer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selectorReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/selectorReducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;CACxD;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE/B,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,cAAc,GACrB,aAAa,CAuCf"}
1
+ {"version":3,"file":"selectorReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/selectorReducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IACvD,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAClC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE/B,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EACzC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,GACxB,aAAa,CAAC,CAAC,CAAC,CA+ClB"}
@@ -5,10 +5,12 @@ export function selectorReducer(state, action) {
5
5
  case "MOVE_DOWN":
6
6
  return {
7
7
  ...state,
8
- selectedIndex: Math.min(action.maxIndex, state.selectedIndex + 1),
8
+ selectedIndex: Math.min(state.items.length - 1, state.selectedIndex + 1),
9
9
  };
10
10
  case "RESET_INDEX":
11
11
  return { ...state, selectedIndex: 0 };
12
+ case "SET_ITEMS":
13
+ return { ...state, items: action.items, selectedIndex: 0 };
12
14
  case "HANDLE_KEY":
13
15
  if (action.key.upArrow) {
14
16
  return {
@@ -19,7 +21,7 @@ export function selectorReducer(state, action) {
19
21
  if (action.key.downArrow) {
20
22
  return {
21
23
  ...state,
22
- selectedIndex: Math.min(action.maxIndex, state.selectedIndex + 1),
24
+ selectedIndex: Math.min(state.items.length - 1, state.selectedIndex + 1),
23
25
  };
24
26
  }
25
27
  if (action.key.return) {
@@ -1 +1 @@
1
- {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/utils/worktree.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,CA2HzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CA6D7D"}
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/utils/worktree.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,CAiIzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAiE7D"}
@@ -1,4 +1,4 @@
1
- import { execSync } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import * as fs from "node:fs";
4
4
  import { getDefaultRemoteBranch, getGitMainRepoRoot } from "wave-agent-sdk";
@@ -33,7 +33,7 @@ export function createWorktree(name, cwd) {
33
33
  }
34
34
  try {
35
35
  // Create worktree and branch
36
- execSync(`git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`, {
36
+ execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, baseBranch], {
37
37
  cwd: repoRoot,
38
38
  stdio: ["ignore", "pipe", "pipe"],
39
39
  });
@@ -52,7 +52,7 @@ export function createWorktree(name, cwd) {
52
52
  if (stderr.includes("already exists")) {
53
53
  // If branch already exists, try to add worktree without -b
54
54
  try {
55
- execSync(`git worktree add "${worktreePath}" ${branchName}`, {
55
+ execFileSync("git", ["worktree", "add", worktreePath, branchName], {
56
56
  cwd: repoRoot,
57
57
  stdio: ["ignore", "pipe", "pipe"],
58
58
  });
@@ -75,11 +75,11 @@ export function createWorktree(name, cwd) {
75
75
  // Base branch not fetched yet — try fetching then retrying
76
76
  const branchNameOnly = baseBranch.split("/").pop();
77
77
  try {
78
- execSync(`git fetch origin ${branchNameOnly}`, {
78
+ execFileSync("git", ["fetch", "origin", branchNameOnly], {
79
79
  cwd: repoRoot,
80
80
  stdio: ["ignore", "pipe", "pipe"],
81
81
  });
82
- execSync(`git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`, {
82
+ execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, baseBranch], {
83
83
  cwd: repoRoot,
84
84
  stdio: ["ignore", "pipe", "pipe"],
85
85
  });
@@ -96,7 +96,7 @@ export function createWorktree(name, cwd) {
96
96
  catch {
97
97
  // Fetch or retry failed — fall back to HEAD
98
98
  try {
99
- execSync(`git worktree add -b ${branchName} "${worktreePath}" HEAD`, {
99
+ execFileSync("git", ["worktree", "add", "-b", branchName, worktreePath, "HEAD"], {
100
100
  cwd: repoRoot,
101
101
  stdio: ["ignore", "pipe", "pipe"],
102
102
  });
@@ -128,7 +128,7 @@ export function removeWorktree(session) {
128
128
  // Get current branch in worktree before removing it
129
129
  let currentBranch;
130
130
  try {
131
- currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`, {
131
+ currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
132
132
  cwd: session.path,
133
133
  encoding: "utf8",
134
134
  stdio: ["ignore", "pipe", "ignore"],
@@ -138,13 +138,13 @@ export function removeWorktree(session) {
138
138
  // Ignore errors getting current branch
139
139
  }
140
140
  // Remove worktree
141
- execSync(`git worktree remove --force "${session.path}"`, {
141
+ execFileSync("git", ["worktree", "remove", "--force", session.path], {
142
142
  cwd: repoRoot,
143
143
  stdio: ["ignore", "pipe", "pipe"],
144
144
  });
145
145
  // Delete original branch
146
146
  try {
147
- execSync(`git branch -D ${session.branch}`, {
147
+ execFileSync("git", ["branch", "-D", session.branch], {
148
148
  cwd: repoRoot,
149
149
  stdio: ["ignore", "pipe", "pipe"],
150
150
  });
@@ -162,7 +162,7 @@ export function removeWorktree(session) {
162
162
  currentBranch !== "main" &&
163
163
  currentBranch !== "master") {
164
164
  try {
165
- execSync(`git branch -D ${currentBranch}`, {
165
+ execFileSync("git", ["branch", "-D", currentBranch], {
166
166
  cwd: repoRoot,
167
167
  stdio: ["ignore", "pipe", "pipe"],
168
168
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,7 +42,7 @@
42
42
  "semver": "^7.7.4",
43
43
  "yargs": "^17.7.2",
44
44
  "zod": "^3.23.8",
45
- "wave-agent-sdk": "0.18.3"
45
+ "wave-agent-sdk": "0.18.5"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/react": "^19.1.8",
package/src/acp/agent.ts CHANGED
@@ -245,6 +245,8 @@ export class WaveAcpAgent implements AcpAgent {
245
245
  callbacks.onSessionIdChange?.(newSessionId),
246
246
  onLatestTotalTokensChange: (tokens: number) =>
247
247
  callbacks.onLatestTotalTokensChange?.(tokens),
248
+ onLoadingChange: (loading: boolean) =>
249
+ callbacks.onLoadingChange?.(loading),
248
250
  },
249
251
  });
250
252
 
@@ -1398,6 +1400,18 @@ export class WaveAcpAgent implements AcpAgent {
1398
1400
  },
1399
1401
  });
1400
1402
  },
1403
+ onLoadingChange: (loading: boolean) => {
1404
+ if (!loading) {
1405
+ this.connection.sessionUpdate({
1406
+ sessionId: sessionRef.id as AcpSessionId,
1407
+ update: {
1408
+ sessionUpdate: "agent_message_chunk",
1409
+ content: { type: "text", text: "" },
1410
+ _meta: { endOfTurn: true },
1411
+ },
1412
+ });
1413
+ }
1414
+ },
1401
1415
  },
1402
1416
  sessionRef,
1403
1417
  };
@@ -23,20 +23,16 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
23
23
  commands = [],
24
24
  }) => {
25
25
  const MAX_VISIBLE_ITEMS = 3;
26
- const [state, dispatch] = useReducer(selectorReducer, {
26
+ const [state, dispatch] = useReducer(selectorReducer<SlashCommand>, {
27
27
  selectedIndex: 0,
28
28
  pendingDecision: null,
29
- } as SelectorState);
29
+ items: [],
30
+ } as SelectorState<SlashCommand>);
30
31
 
31
- const { selectedIndex, pendingDecision } = state;
32
-
33
- // Reset selected index when search query changes
34
- useEffect(() => {
35
- dispatch({ type: "RESET_INDEX" });
36
- }, [searchQuery]);
32
+ const { selectedIndex, pendingDecision, items: filteredCommands } = state;
37
33
 
38
34
  // Merge agent commands and local commands, memoized to stabilize useEffect dependencies
39
- const filteredCommands = useMemo(() => {
35
+ const computedCommands = useMemo(() => {
40
36
  const allCommands = [...AVAILABLE_COMMANDS, ...commands];
41
37
  return allCommands.filter(
42
38
  (command) =>
@@ -45,6 +41,11 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
45
41
  );
46
42
  }, [searchQuery, commands]);
47
43
 
44
+ // Sync computed commands into reducer state (SET_ITEMS also resets index)
45
+ useEffect(() => {
46
+ dispatch({ type: "SET_ITEMS", items: computedCommands });
47
+ }, [computedCommands]);
48
+
48
49
  // Handle decisions from reducer
49
50
  useEffect(() => {
50
51
  if (!pendingDecision) return;
@@ -91,12 +92,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
91
92
  );
92
93
 
93
94
  useInput((input, key) => {
94
- dispatch({
95
- type: "HANDLE_KEY",
96
- key,
97
- maxIndex: filteredCommands.length - 1,
98
- hasInsert: !!onInsert,
99
- });
95
+ dispatch({ type: "HANDLE_KEY", key, hasInsert: !!onInsert });
100
96
  });
101
97
 
102
98
  if (filteredCommands.length === 0) {
@@ -108,7 +108,6 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
108
108
  type: "HANDLE_KEY",
109
109
  input,
110
110
  key,
111
- currentQuestion,
112
111
  questions,
113
112
  });
114
113
  } else {
@@ -1,37 +1,52 @@
1
1
  import React, { useReducer, useEffect } from "react";
2
2
  import { Box, useInput } from "ink";
3
+ import type { MarketplacePluginEntry } from "wave-agent-sdk";
3
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
5
  import { PluginList } from "./PluginList.js";
5
- import { selectorReducer } from "../reducers/selectorReducer.js";
6
+ import {
7
+ selectorReducer,
8
+ type SelectorState,
9
+ } from "../reducers/selectorReducer.js";
10
+
11
+ type DiscoverablePlugin = MarketplacePluginEntry & {
12
+ marketplace: string;
13
+ installed: boolean;
14
+ version?: string;
15
+ };
6
16
 
7
17
  export const DiscoverView: React.FC = () => {
8
18
  const { discoverablePlugins, actions } = usePluginManagerContext();
9
- const [state, dispatch] = useReducer(selectorReducer, {
19
+ const [state, dispatch] = useReducer(selectorReducer<DiscoverablePlugin>, {
10
20
  selectedIndex: 0,
11
21
  pendingDecision: null,
12
- });
22
+ items: [],
23
+ } as SelectorState<DiscoverablePlugin>);
13
24
 
14
- const { selectedIndex, pendingDecision } = state;
25
+ const { selectedIndex, pendingDecision, items } = state;
26
+
27
+ // Sync plugins into reducer state
28
+ useEffect(() => {
29
+ dispatch({ type: "SET_ITEMS", items: discoverablePlugins });
30
+ }, [discoverablePlugins]);
15
31
 
16
32
  useInput((_input, key) => {
17
33
  dispatch({
18
34
  type: "HANDLE_KEY",
19
35
  key,
20
- maxIndex: discoverablePlugins.length - 1,
21
36
  hasInsert: false,
22
37
  });
23
38
  });
24
39
 
25
40
  useEffect(() => {
26
41
  if (pendingDecision === "select") {
27
- const plugin = discoverablePlugins[selectedIndex];
42
+ const plugin = items[selectedIndex];
28
43
  if (plugin) {
29
44
  actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
30
45
  actions.setView("PLUGIN_DETAIL");
31
46
  }
32
47
  dispatch({ type: "CLEAR_DECISION" });
33
48
  }
34
- }, [pendingDecision, selectedIndex, discoverablePlugins, actions]);
49
+ }, [pendingDecision, selectedIndex, items, actions]);
35
50
 
36
51
  return (
37
52
  <Box flexDirection="column">
@@ -23,16 +23,17 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
23
23
  onSelect,
24
24
  onCancel,
25
25
  }) => {
26
- const [state, dispatch] = useReducer(selectorReducer, {
26
+ const [state, dispatch] = useReducer(selectorReducer<FileItem>, {
27
27
  selectedIndex: 0,
28
28
  pendingDecision: null,
29
- } as SelectorState);
29
+ items: [],
30
+ } as SelectorState<FileItem>);
30
31
 
31
- const { selectedIndex, pendingDecision } = state;
32
+ const { selectedIndex, pendingDecision, items: filesInState } = state;
32
33
 
33
- // Reset selected index when files change
34
+ // Sync files from props into reducer state (SET_ITEMS also resets index)
34
35
  useEffect(() => {
35
- dispatch({ type: "RESET_INDEX" });
36
+ dispatch({ type: "SET_ITEMS", items: files });
36
37
  }, [files]);
37
38
 
38
39
  // Handle decisions from reducer
@@ -40,21 +41,20 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
40
41
  if (!pendingDecision) return;
41
42
 
42
43
  if (pendingDecision === "select" || pendingDecision === "insert") {
43
- if (files.length > 0 && selectedIndex < files.length) {
44
- onSelect(files[selectedIndex].path);
44
+ if (filesInState.length > 0 && selectedIndex < filesInState.length) {
45
+ onSelect(filesInState[selectedIndex].path);
45
46
  }
46
47
  } else if (pendingDecision === "cancel") {
47
48
  onCancel();
48
49
  }
49
50
 
50
51
  dispatch({ type: "CLEAR_DECISION" });
51
- }, [pendingDecision, selectedIndex, files, onSelect, onCancel]);
52
+ }, [pendingDecision, selectedIndex, filesInState, onSelect, onCancel]);
52
53
 
53
54
  useInput((input, key) => {
54
55
  dispatch({
55
56
  type: "HANDLE_KEY",
56
57
  key,
57
- maxIndex: files.length - 1,
58
58
  hasInsert: true, // For FileSelector, Tab is also select
59
59
  });
60
60
  });
@@ -1,4 +1,4 @@
1
- import React, { useReducer, useEffect, useState } from "react";
1
+ import React, { useReducer, useEffect } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { PromptHistoryManager, type PromptEntry } from "wave-agent-sdk";
4
4
  import {
@@ -18,20 +18,18 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
18
18
  onCancel,
19
19
  }) => {
20
20
  const MAX_VISIBLE_ITEMS = 5;
21
- const [state, dispatch] = useReducer(selectorReducer, {
21
+ const [state, dispatch] = useReducer(selectorReducer<PromptEntry>, {
22
22
  selectedIndex: 0,
23
23
  pendingDecision: null,
24
- } as SelectorState);
25
- const [entries, setEntries] = useState<PromptEntry[]>([]);
24
+ items: [],
25
+ } as SelectorState<PromptEntry>);
26
26
 
27
- const { selectedIndex, pendingDecision } = state;
27
+ const { selectedIndex, pendingDecision, items: entries } = state;
28
28
 
29
29
  useEffect(() => {
30
30
  const fetchHistory = async () => {
31
31
  const results = await PromptHistoryManager.searchHistory(searchQuery);
32
- const limitedResults = results.slice(0, 20);
33
- setEntries(limitedResults);
34
- dispatch({ type: "RESET_INDEX" });
32
+ dispatch({ type: "SET_ITEMS", items: results.slice(0, 20) });
35
33
  };
36
34
  fetchHistory();
37
35
  }, [searchQuery]);
@@ -52,12 +50,7 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
52
50
  }, [pendingDecision, selectedIndex, entries, onSelect, onCancel]);
53
51
 
54
52
  useInput((input, key) => {
55
- dispatch({
56
- type: "HANDLE_KEY",
57
- key,
58
- maxIndex: entries.length - 1,
59
- hasInsert: false,
60
- });
53
+ dispatch({ type: "HANDLE_KEY", key, hasInsert: false });
61
54
  });
62
55
 
63
56
  if (entries.length === 0) {
@@ -1,36 +1,50 @@
1
1
  import React, { useReducer, useEffect } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
+ import type { InstalledPlugin } from "wave-agent-sdk";
3
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
- import { selectorReducer } from "../reducers/selectorReducer.js";
5
+ import {
6
+ selectorReducer,
7
+ type SelectorState,
8
+ } from "../reducers/selectorReducer.js";
9
+
10
+ type InstalledPluginWithEnabled = InstalledPlugin & { enabled: boolean };
5
11
 
6
12
  export const InstalledView: React.FC = () => {
7
13
  const { installedPlugins, actions } = usePluginManagerContext();
8
- const [state, dispatch] = useReducer(selectorReducer, {
9
- selectedIndex: 0,
10
- pendingDecision: null,
11
- });
14
+ const [state, dispatch] = useReducer(
15
+ selectorReducer<InstalledPluginWithEnabled>,
16
+ {
17
+ selectedIndex: 0,
18
+ pendingDecision: null,
19
+ items: [],
20
+ } as SelectorState<InstalledPluginWithEnabled>,
21
+ );
12
22
 
13
- const { selectedIndex, pendingDecision } = state;
23
+ const { selectedIndex, pendingDecision, items } = state;
24
+
25
+ // Sync plugins into reducer state
26
+ useEffect(() => {
27
+ dispatch({ type: "SET_ITEMS", items: installedPlugins });
28
+ }, [installedPlugins]);
14
29
 
15
30
  useInput((_input, key) => {
16
31
  dispatch({
17
32
  type: "HANDLE_KEY",
18
33
  key,
19
- maxIndex: installedPlugins.length - 1,
20
34
  hasInsert: false,
21
35
  });
22
36
  });
23
37
 
24
38
  useEffect(() => {
25
39
  if (pendingDecision === "select") {
26
- const plugin = installedPlugins[selectedIndex];
40
+ const plugin = items[selectedIndex];
27
41
  if (plugin) {
28
42
  actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
29
43
  actions.setView("PLUGIN_DETAIL");
30
44
  }
31
45
  dispatch({ type: "CLEAR_DECISION" });
32
46
  }
33
- }, [pendingDecision, selectedIndex, installedPlugins, actions]);
47
+ }, [pendingDecision, selectedIndex, items, actions]);
34
48
 
35
49
  if (installedPlugins.length === 0) {
36
50
  return (
@@ -36,49 +36,46 @@ export const LoginCommand: React.FC<LoginCommandProps> = ({ onCancel }) => {
36
36
  const tokenResolveRef = useRef<((token: string) => void) | null>(null);
37
37
  const tokenRejectRef = useRef<((err: Error) => void) | null>(null);
38
38
 
39
- // Pre-loading input: Enter starts login, Esc cancels
40
- useInput(
41
- (_, key) => {
39
+ // Single useInput handler branches on isLoading to avoid the
40
+ // isActive transition race that drops stdin between hook swaps.
41
+ useInput((input, key) => {
42
+ if (!isLoadingRef.current) {
43
+ // Pre-loading mode: Enter starts login, Esc cancels
42
44
  if (key.escape) {
43
45
  onCancel();
44
46
  }
45
- if (key.return && !isLoadingRef.current) {
47
+ if (key.return) {
46
48
  handleEnter();
47
49
  }
48
- },
49
- { isActive: !isLoading },
50
- );
50
+ return;
51
+ }
51
52
 
52
- // Token input: capture keystrokes while loading
53
- useInput(
54
- (input, key) => {
55
- if (key.escape) {
56
- tokenRejectRef.current?.(new Error("cancelled"));
57
- onCancel();
58
- }
59
- if (key.return) {
60
- // Submit token
61
- const trimmed = tokenInput.trim();
62
- if (trimmed) {
63
- tokenResolveRef.current?.(trimmed);
64
- } else {
65
- // Empty: just clear, keep waiting
66
- setTokenInput("");
67
- }
68
- return;
69
- }
70
- // Backspace
71
- if (key.backspace && tokenInput.length > 0) {
72
- setTokenInput((prev) => prev.slice(0, -1));
73
- return;
74
- }
75
- // Regular character input (single or pasted multi-char)
76
- if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
77
- setTokenInput((prev) => prev + input);
53
+ // Token input mode: capture keystrokes while loading
54
+ if (key.escape) {
55
+ tokenRejectRef.current?.(new Error("cancelled"));
56
+ onCancel();
57
+ }
58
+ if (key.return) {
59
+ // Submit token
60
+ const trimmed = tokenInput.trim();
61
+ if (trimmed) {
62
+ tokenResolveRef.current?.(trimmed);
63
+ } else {
64
+ // Empty: just clear, keep waiting
65
+ setTokenInput("");
78
66
  }
79
- },
80
- { isActive: isLoading },
81
- );
67
+ return;
68
+ }
69
+ // Backspace
70
+ if (key.backspace && tokenInput.length > 0) {
71
+ setTokenInput((prev) => prev.slice(0, -1));
72
+ return;
73
+ }
74
+ // Regular character input (single or pasted multi-char)
75
+ if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
76
+ setTokenInput((prev) => prev + input);
77
+ }
78
+ });
82
79
 
83
80
  const handleEnter = async () => {
84
81
  if (isLoadingRef.current) return;
@@ -1,16 +1,27 @@
1
1
  import React, { useReducer, useEffect, useMemo } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
- import { selectorReducer } from "../reducers/selectorReducer.js";
4
+ import {
5
+ selectorReducer,
6
+ type SelectorState,
7
+ } from "../reducers/selectorReducer.js";
5
8
 
6
9
  export const MarketplaceDetail: React.FC = () => {
7
10
  const { state, marketplaces, actions } = usePluginManagerContext();
8
- const [selectorState, dispatch] = useReducer(selectorReducer, {
9
- selectedIndex: 0,
10
- pendingDecision: null,
11
- });
11
+ const [selectorState, dispatch] = useReducer(
12
+ selectorReducer<{ id: string; label: string }>,
13
+ {
14
+ selectedIndex: 0,
15
+ pendingDecision: null,
16
+ items: [],
17
+ } as SelectorState<{ id: string; label: string }>,
18
+ );
12
19
 
13
- const { selectedIndex: selectedActionIndex, pendingDecision } = selectorState;
20
+ const {
21
+ selectedIndex: selectedActionIndex,
22
+ pendingDecision,
23
+ items: actionsList,
24
+ } = selectorState;
14
25
 
15
26
  const marketplace = marketplaces.find((m) => m.name === state.selectedId);
16
27
 
@@ -27,13 +38,17 @@ export const MarketplaceDetail: React.FC = () => {
27
38
  [marketplace?.autoUpdate],
28
39
  );
29
40
 
41
+ // Sync ACTIONS into reducer state
42
+ useEffect(() => {
43
+ dispatch({ type: "SET_ITEMS", items: [...ACTIONS] });
44
+ }, [ACTIONS]);
45
+
30
46
  useInput((_input, key) => {
31
47
  if (state.isLoading && !key.escape) return;
32
48
 
33
49
  dispatch({
34
50
  type: "HANDLE_KEY",
35
51
  key,
36
- maxIndex: ACTIONS.length - 1,
37
52
  hasInsert: false,
38
53
  });
39
54
  });
@@ -42,12 +57,12 @@ export const MarketplaceDetail: React.FC = () => {
42
57
  if (!pendingDecision) return;
43
58
 
44
59
  if (pendingDecision === "select" && marketplace && !state.isLoading) {
45
- const action = ACTIONS[selectedActionIndex].id;
60
+ const action = actionsList[selectedActionIndex]?.id;
46
61
  if (action === "toggle-auto-update") {
47
62
  actions.toggleAutoUpdate(marketplace.name, !marketplace.autoUpdate);
48
63
  } else if (action === "update") {
49
64
  actions.updateMarketplace(marketplace.name);
50
- } else {
65
+ } else if (action === "remove") {
51
66
  actions.removeMarketplace(marketplace.name);
52
67
  }
53
68
  } else if (pendingDecision === "cancel") {
@@ -58,10 +73,10 @@ export const MarketplaceDetail: React.FC = () => {
58
73
  }, [
59
74
  pendingDecision,
60
75
  selectedActionIndex,
76
+ actionsList,
61
77
  marketplace,
62
78
  state.isLoading,
63
79
  actions,
64
- ACTIONS,
65
80
  ]);
66
81
 
67
82
  if (!marketplace) {