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.
- package/dist/acp/agent.d.ts.map +1 -1
- package/dist/acp/agent.js +13 -0
- package/dist/components/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +9 -13
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +0 -1
- package/dist/components/DiscoverView.d.ts.map +1 -1
- package/dist/components/DiscoverView.js +10 -6
- package/dist/components/FileSelector.js +8 -8
- package/dist/components/HistorySearch.d.ts.map +1 -1
- package/dist/components/HistorySearch.js +6 -13
- package/dist/components/InstalledView.d.ts.map +1 -1
- package/dist/components/InstalledView.js +10 -6
- package/dist/components/LoginCommand.d.ts.map +1 -1
- package/dist/components/LoginCommand.js +14 -11
- package/dist/components/MarketplaceDetail.d.ts.map +1 -1
- package/dist/components/MarketplaceDetail.js +11 -7
- package/dist/components/MarketplaceView.d.ts.map +1 -1
- package/dist/components/MarketplaceView.js +10 -6
- package/dist/components/ModelSelector.d.ts.map +1 -1
- package/dist/components/ModelSelector.js +9 -15
- package/dist/components/SessionSelector.d.ts +5 -3
- package/dist/components/SessionSelector.d.ts.map +1 -1
- package/dist/components/SessionSelector.js +11 -7
- package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
- package/dist/components/WorktreeExitPrompt.js +3 -3
- package/dist/reducers/questionReducer.d.ts +0 -7
- package/dist/reducers/questionReducer.d.ts.map +1 -1
- package/dist/reducers/questionReducer.js +2 -1
- package/dist/reducers/selectorReducer.d.ts +7 -5
- package/dist/reducers/selectorReducer.d.ts.map +1 -1
- package/dist/reducers/selectorReducer.js +4 -2
- package/dist/utils/worktree.d.ts.map +1 -1
- package/dist/utils/worktree.js +10 -10
- package/package.json +2 -2
- package/src/acp/agent.ts +14 -0
- package/src/components/CommandSelector.tsx +11 -15
- package/src/components/ConfirmationSelector.tsx +0 -1
- package/src/components/DiscoverView.tsx +22 -7
- package/src/components/FileSelector.tsx +9 -9
- package/src/components/HistorySearch.tsx +7 -14
- package/src/components/InstalledView.tsx +23 -9
- package/src/components/LoginCommand.tsx +33 -36
- package/src/components/MarketplaceDetail.tsx +25 -10
- package/src/components/MarketplaceView.tsx +16 -7
- package/src/components/ModelSelector.tsx +13 -16
- package/src/components/SessionSelector.tsx +19 -9
- package/src/components/WorktreeExitPrompt.tsx +7 -4
- package/src/reducers/questionReducer.ts +2 -6
- package/src/reducers/selectorReducer.ts +20 -10
- package/src/utils/worktree.ts +29 -19
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import type { KnownMarketplace } from "wave-agent-sdk";
|
|
3
4
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
selectorReducer,
|
|
7
|
+
type SelectorState,
|
|
8
|
+
} from "../reducers/selectorReducer.js";
|
|
5
9
|
|
|
6
10
|
import { MarketplaceList } from "./MarketplaceList.js";
|
|
7
11
|
|
|
8
12
|
export const MarketplaceView: React.FC = () => {
|
|
9
13
|
const { marketplaces, actions } = usePluginManagerContext();
|
|
10
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
14
|
+
const [state, dispatch] = useReducer(selectorReducer<KnownMarketplace>, {
|
|
11
15
|
selectedIndex: 0,
|
|
12
16
|
pendingDecision: null,
|
|
13
|
-
|
|
17
|
+
items: [],
|
|
18
|
+
} as SelectorState<KnownMarketplace>);
|
|
19
|
+
|
|
20
|
+
const { selectedIndex, pendingDecision, items } = state;
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
// Sync marketplaces into reducer state
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
dispatch({ type: "SET_ITEMS", items: marketplaces });
|
|
25
|
+
}, [marketplaces]);
|
|
16
26
|
|
|
17
27
|
useInput((input, key) => {
|
|
18
28
|
if (input === "a") {
|
|
@@ -23,21 +33,20 @@ export const MarketplaceView: React.FC = () => {
|
|
|
23
33
|
dispatch({
|
|
24
34
|
type: "HANDLE_KEY",
|
|
25
35
|
key,
|
|
26
|
-
maxIndex: marketplaces.length - 1,
|
|
27
36
|
hasInsert: false,
|
|
28
37
|
});
|
|
29
38
|
});
|
|
30
39
|
|
|
31
40
|
useEffect(() => {
|
|
32
41
|
if (pendingDecision === "select") {
|
|
33
|
-
const mk =
|
|
42
|
+
const mk = items[selectedIndex];
|
|
34
43
|
if (mk) {
|
|
35
44
|
actions.setSelectedId(mk.name);
|
|
36
45
|
actions.setView("MARKETPLACE_DETAIL");
|
|
37
46
|
}
|
|
38
47
|
dispatch({ type: "CLEAR_DECISION" });
|
|
39
48
|
}
|
|
40
|
-
}, [pendingDecision, selectedIndex,
|
|
49
|
+
}, [pendingDecision, selectedIndex, items, actions]);
|
|
41
50
|
|
|
42
51
|
return (
|
|
43
52
|
<Box flexDirection="column">
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
selectorReducer,
|
|
5
|
+
type SelectorState,
|
|
6
|
+
} from "../reducers/selectorReducer.js";
|
|
4
7
|
|
|
5
8
|
export interface ModelSelectorProps {
|
|
6
9
|
onCancel: () => void;
|
|
@@ -17,29 +20,29 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
17
20
|
configuredModels,
|
|
18
21
|
onSelectModel,
|
|
19
22
|
}) => {
|
|
20
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
23
|
+
const [state, dispatch] = useReducer(selectorReducer<string>, {
|
|
21
24
|
selectedIndex:
|
|
22
25
|
configuredModels.indexOf(currentModel) !== -1
|
|
23
26
|
? configuredModels.indexOf(currentModel)
|
|
24
27
|
: 0,
|
|
25
28
|
pendingDecision: null,
|
|
26
|
-
|
|
29
|
+
items: configuredModels,
|
|
30
|
+
} as SelectorState<string>);
|
|
27
31
|
|
|
28
|
-
const { selectedIndex, pendingDecision } = state;
|
|
32
|
+
const { selectedIndex, pendingDecision, items: models } = state;
|
|
29
33
|
|
|
30
34
|
useInput((_input, key) => {
|
|
31
35
|
dispatch({
|
|
32
36
|
type: "HANDLE_KEY",
|
|
33
37
|
key,
|
|
34
|
-
maxIndex: configuredModels.length - 1,
|
|
35
38
|
hasInsert: false,
|
|
36
39
|
});
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
useEffect(() => {
|
|
40
43
|
if (pendingDecision === "select") {
|
|
41
|
-
if (
|
|
42
|
-
onSelectModel(
|
|
44
|
+
if (models.length > 0) {
|
|
45
|
+
onSelectModel(models[selectedIndex]);
|
|
43
46
|
}
|
|
44
47
|
onCancel();
|
|
45
48
|
dispatch({ type: "CLEAR_DECISION" });
|
|
@@ -47,23 +50,17 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
47
50
|
onCancel();
|
|
48
51
|
dispatch({ type: "CLEAR_DECISION" });
|
|
49
52
|
}
|
|
50
|
-
}, [
|
|
51
|
-
pendingDecision,
|
|
52
|
-
selectedIndex,
|
|
53
|
-
configuredModels,
|
|
54
|
-
onSelectModel,
|
|
55
|
-
onCancel,
|
|
56
|
-
]);
|
|
53
|
+
}, [pendingDecision, selectedIndex, models, onSelectModel, onCancel]);
|
|
57
54
|
|
|
58
55
|
// Calculate visible window
|
|
59
56
|
const startIndex = Math.max(
|
|
60
57
|
0,
|
|
61
58
|
Math.min(
|
|
62
59
|
selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
|
|
63
|
-
Math.max(0,
|
|
60
|
+
Math.max(0, models.length - MAX_VISIBLE_ITEMS),
|
|
64
61
|
),
|
|
65
62
|
);
|
|
66
|
-
const visibleModels =
|
|
63
|
+
const visibleModels = models.slice(
|
|
67
64
|
startIndex,
|
|
68
65
|
startIndex + MAX_VISIBLE_ITEMS,
|
|
69
66
|
);
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import type { SessionMetadata } from "wave-agent-sdk";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
selectorReducer,
|
|
6
|
+
type SelectorState,
|
|
7
|
+
} from "../reducers/selectorReducer.js";
|
|
8
|
+
|
|
9
|
+
type SessionItem = SessionMetadata & { firstMessage?: string };
|
|
5
10
|
|
|
6
11
|
export interface SessionSelectorProps {
|
|
7
|
-
sessions:
|
|
12
|
+
sessions: SessionItem[];
|
|
8
13
|
onSelect: (sessionId: string) => void;
|
|
9
14
|
onCancel: () => void;
|
|
10
15
|
}
|
|
@@ -14,33 +19,38 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
|
|
14
19
|
onSelect,
|
|
15
20
|
onCancel,
|
|
16
21
|
}) => {
|
|
17
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
22
|
+
const [state, dispatch] = useReducer(selectorReducer<SessionItem>, {
|
|
18
23
|
selectedIndex: 0,
|
|
19
24
|
pendingDecision: null,
|
|
20
|
-
|
|
25
|
+
items: [],
|
|
26
|
+
} as SelectorState<SessionItem>);
|
|
21
27
|
|
|
22
|
-
const { selectedIndex, pendingDecision } = state;
|
|
28
|
+
const { selectedIndex, pendingDecision, items } = state;
|
|
29
|
+
|
|
30
|
+
// Sync sessions into reducer state
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
dispatch({ type: "SET_ITEMS", items: sessions });
|
|
33
|
+
}, [sessions]);
|
|
23
34
|
|
|
24
35
|
useInput((_input, key) => {
|
|
25
36
|
dispatch({
|
|
26
37
|
type: "HANDLE_KEY",
|
|
27
38
|
key,
|
|
28
|
-
maxIndex: sessions.length - 1,
|
|
29
39
|
hasInsert: false,
|
|
30
40
|
});
|
|
31
41
|
});
|
|
32
42
|
|
|
33
43
|
useEffect(() => {
|
|
34
44
|
if (pendingDecision === "select") {
|
|
35
|
-
if (
|
|
36
|
-
onSelect(
|
|
45
|
+
if (items.length > 0 && selectedIndex < items.length) {
|
|
46
|
+
onSelect(items[selectedIndex].id);
|
|
37
47
|
}
|
|
38
48
|
dispatch({ type: "CLEAR_DECISION" });
|
|
39
49
|
} else if (pendingDecision === "cancel") {
|
|
40
50
|
onCancel();
|
|
41
51
|
dispatch({ type: "CLEAR_DECISION" });
|
|
42
52
|
}
|
|
43
|
-
}, [pendingDecision, selectedIndex,
|
|
53
|
+
}, [pendingDecision, selectedIndex, items, onSelect, onCancel]);
|
|
44
54
|
|
|
45
55
|
if (sessions.length === 0) {
|
|
46
56
|
return (
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useReducer, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
selectorReducer,
|
|
5
|
+
type SelectorState,
|
|
6
|
+
} from "../reducers/selectorReducer.js";
|
|
4
7
|
|
|
5
8
|
interface WorktreeExitPromptProps {
|
|
6
9
|
name: string;
|
|
@@ -21,10 +24,11 @@ export const WorktreeExitPrompt: React.FC<WorktreeExitPromptProps> = ({
|
|
|
21
24
|
onRemove,
|
|
22
25
|
onCancel,
|
|
23
26
|
}) => {
|
|
24
|
-
const [state, dispatch] = useReducer(selectorReducer
|
|
27
|
+
const [state, dispatch] = useReducer(selectorReducer<string>, {
|
|
25
28
|
selectedIndex: 0,
|
|
26
29
|
pendingDecision: null,
|
|
27
|
-
|
|
30
|
+
items: ["keep", "remove"],
|
|
31
|
+
} as SelectorState<string>);
|
|
28
32
|
|
|
29
33
|
const { selectedIndex, pendingDecision } = state;
|
|
30
34
|
|
|
@@ -32,7 +36,6 @@ export const WorktreeExitPrompt: React.FC<WorktreeExitPromptProps> = ({
|
|
|
32
36
|
dispatch({
|
|
33
37
|
type: "HANDLE_KEY",
|
|
34
38
|
key,
|
|
35
|
-
maxIndex: 1,
|
|
36
39
|
hasInsert: false,
|
|
37
40
|
});
|
|
38
41
|
});
|
|
@@ -52,11 +52,6 @@ export type QuestionAction =
|
|
|
52
52
|
type: "HANDLE_KEY";
|
|
53
53
|
input: string;
|
|
54
54
|
key: Key;
|
|
55
|
-
currentQuestion: {
|
|
56
|
-
question: string;
|
|
57
|
-
options: Array<{ label: string }>;
|
|
58
|
-
multiSelect?: boolean;
|
|
59
|
-
};
|
|
60
55
|
questions: Array<{
|
|
61
56
|
question: string;
|
|
62
57
|
options: Array<{ label: string }>;
|
|
@@ -265,7 +260,8 @@ export function questionReducer(
|
|
|
265
260
|
case "CLEAR_DECISION":
|
|
266
261
|
return { ...state, decision: null };
|
|
267
262
|
case "HANDLE_KEY": {
|
|
268
|
-
const { input, key,
|
|
263
|
+
const { input, key, questions } = action;
|
|
264
|
+
const currentQuestion = questions[state.currentQuestionIndex];
|
|
269
265
|
if (!currentQuestion) return state;
|
|
270
266
|
|
|
271
267
|
const options = [...currentQuestion.options, { label: "Other" }];
|
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
import { Key } from "ink";
|
|
2
2
|
|
|
3
|
-
export interface SelectorState {
|
|
3
|
+
export interface SelectorState<T = unknown> {
|
|
4
4
|
selectedIndex: number;
|
|
5
5
|
pendingDecision: "select" | "insert" | "cancel" | null;
|
|
6
|
+
items: T[];
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export type SelectorAction =
|
|
9
|
+
export type SelectorAction<T = unknown> =
|
|
9
10
|
| { type: "MOVE_UP" }
|
|
10
|
-
| { type: "MOVE_DOWN"
|
|
11
|
+
| { type: "MOVE_DOWN" }
|
|
11
12
|
| { type: "RESET_INDEX" }
|
|
12
|
-
| { type: "
|
|
13
|
+
| { type: "SET_ITEMS"; items: T[] }
|
|
14
|
+
| { type: "HANDLE_KEY"; key: Key; hasInsert: boolean }
|
|
13
15
|
| { type: "CLEAR_DECISION" };
|
|
14
16
|
|
|
15
|
-
export function selectorReducer(
|
|
16
|
-
state: SelectorState
|
|
17
|
-
action: SelectorAction
|
|
18
|
-
): SelectorState {
|
|
17
|
+
export function selectorReducer<T = unknown>(
|
|
18
|
+
state: SelectorState<T>,
|
|
19
|
+
action: SelectorAction<T>,
|
|
20
|
+
): SelectorState<T> {
|
|
19
21
|
switch (action.type) {
|
|
20
22
|
case "MOVE_UP":
|
|
21
23
|
return { ...state, selectedIndex: Math.max(0, state.selectedIndex - 1) };
|
|
22
24
|
case "MOVE_DOWN":
|
|
23
25
|
return {
|
|
24
26
|
...state,
|
|
25
|
-
selectedIndex: Math.min(
|
|
27
|
+
selectedIndex: Math.min(
|
|
28
|
+
state.items.length - 1,
|
|
29
|
+
state.selectedIndex + 1,
|
|
30
|
+
),
|
|
26
31
|
};
|
|
27
32
|
case "RESET_INDEX":
|
|
28
33
|
return { ...state, selectedIndex: 0 };
|
|
34
|
+
case "SET_ITEMS":
|
|
35
|
+
return { ...state, items: action.items, selectedIndex: 0 };
|
|
29
36
|
case "HANDLE_KEY":
|
|
30
37
|
if (action.key.upArrow) {
|
|
31
38
|
return {
|
|
@@ -36,7 +43,10 @@ export function selectorReducer(
|
|
|
36
43
|
if (action.key.downArrow) {
|
|
37
44
|
return {
|
|
38
45
|
...state,
|
|
39
|
-
selectedIndex: Math.min(
|
|
46
|
+
selectedIndex: Math.min(
|
|
47
|
+
state.items.length - 1,
|
|
48
|
+
state.selectedIndex + 1,
|
|
49
|
+
),
|
|
40
50
|
};
|
|
41
51
|
}
|
|
42
52
|
if (action.key.return) {
|
package/src/utils/worktree.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
@@ -47,8 +47,9 @@ export function createWorktree(name: string, cwd: string): WorktreeSession {
|
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
49
|
// Create worktree and branch
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
execFileSync(
|
|
51
|
+
"git",
|
|
52
|
+
["worktree", "add", "-b", branchName, worktreePath, baseBranch],
|
|
52
53
|
{
|
|
53
54
|
cwd: repoRoot,
|
|
54
55
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -69,7 +70,7 @@ export function createWorktree(name: string, cwd: string): WorktreeSession {
|
|
|
69
70
|
if (stderr.includes("already exists")) {
|
|
70
71
|
// If branch already exists, try to add worktree without -b
|
|
71
72
|
try {
|
|
72
|
-
|
|
73
|
+
execFileSync("git", ["worktree", "add", worktreePath, branchName], {
|
|
73
74
|
cwd: repoRoot,
|
|
74
75
|
stdio: ["ignore", "pipe", "pipe"],
|
|
75
76
|
});
|
|
@@ -95,12 +96,13 @@ export function createWorktree(name: string, cwd: string): WorktreeSession {
|
|
|
95
96
|
// Base branch not fetched yet — try fetching then retrying
|
|
96
97
|
const branchNameOnly = baseBranch.split("/").pop()!;
|
|
97
98
|
try {
|
|
98
|
-
|
|
99
|
+
execFileSync("git", ["fetch", "origin", branchNameOnly], {
|
|
99
100
|
cwd: repoRoot,
|
|
100
101
|
stdio: ["ignore", "pipe", "pipe"],
|
|
101
102
|
});
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
execFileSync(
|
|
104
|
+
"git",
|
|
105
|
+
["worktree", "add", "-b", branchName, worktreePath, baseBranch],
|
|
104
106
|
{
|
|
105
107
|
cwd: repoRoot,
|
|
106
108
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -118,10 +120,14 @@ export function createWorktree(name: string, cwd: string): WorktreeSession {
|
|
|
118
120
|
} catch {
|
|
119
121
|
// Fetch or retry failed — fall back to HEAD
|
|
120
122
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
execFileSync(
|
|
124
|
+
"git",
|
|
125
|
+
["worktree", "add", "-b", branchName, worktreePath, "HEAD"],
|
|
126
|
+
{
|
|
127
|
+
cwd: repoRoot,
|
|
128
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
129
|
+
},
|
|
130
|
+
);
|
|
125
131
|
return {
|
|
126
132
|
name,
|
|
127
133
|
path: worktreePath,
|
|
@@ -155,24 +161,28 @@ export function removeWorktree(session: WorktreeSession): void {
|
|
|
155
161
|
// Get current branch in worktree before removing it
|
|
156
162
|
let currentBranch: string | undefined;
|
|
157
163
|
try {
|
|
158
|
-
currentBranch =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
currentBranch = execFileSync(
|
|
165
|
+
"git",
|
|
166
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
167
|
+
{
|
|
168
|
+
cwd: session.path,
|
|
169
|
+
encoding: "utf8",
|
|
170
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
171
|
+
},
|
|
172
|
+
).trim();
|
|
163
173
|
} catch {
|
|
164
174
|
// Ignore errors getting current branch
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
// Remove worktree
|
|
168
|
-
|
|
178
|
+
execFileSync("git", ["worktree", "remove", "--force", session.path], {
|
|
169
179
|
cwd: repoRoot,
|
|
170
180
|
stdio: ["ignore", "pipe", "pipe"],
|
|
171
181
|
});
|
|
172
182
|
|
|
173
183
|
// Delete original branch
|
|
174
184
|
try {
|
|
175
|
-
|
|
185
|
+
execFileSync("git", ["branch", "-D", session.branch], {
|
|
176
186
|
cwd: repoRoot,
|
|
177
187
|
stdio: ["ignore", "pipe", "pipe"],
|
|
178
188
|
});
|
|
@@ -195,7 +205,7 @@ export function removeWorktree(session: WorktreeSession): void {
|
|
|
195
205
|
currentBranch !== "master"
|
|
196
206
|
) {
|
|
197
207
|
try {
|
|
198
|
-
|
|
208
|
+
execFileSync("git", ["branch", "-D", currentBranch], {
|
|
199
209
|
cwd: repoRoot,
|
|
200
210
|
stdio: ["ignore", "pipe", "pipe"],
|
|
201
211
|
});
|