wave-code 0.7.1 → 0.8.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.
Files changed (108) hide show
  1. package/dist/cli.d.ts +2 -4
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +24 -52
  4. package/dist/components/App.d.ts +3 -4
  5. package/dist/components/App.d.ts.map +1 -1
  6. package/dist/components/App.js +49 -6
  7. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  8. package/dist/components/BackgroundTaskManager.js +12 -20
  9. package/dist/components/BangDisplay.d.ts +9 -0
  10. package/dist/components/BangDisplay.d.ts.map +1 -0
  11. package/dist/components/{CommandOutputDisplay.js → BangDisplay.js} +1 -1
  12. package/dist/components/ChatInterface.d.ts.map +1 -1
  13. package/dist/components/ChatInterface.js +3 -2
  14. package/dist/components/CommandSelector.d.ts.map +1 -1
  15. package/dist/components/CommandSelector.js +18 -2
  16. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  17. package/dist/components/ConfirmationSelector.js +105 -8
  18. package/dist/components/HelpView.d.ts.map +1 -1
  19. package/dist/components/HelpView.js +2 -0
  20. package/dist/components/HistorySearch.d.ts.map +1 -1
  21. package/dist/components/HistorySearch.js +19 -25
  22. package/dist/components/InputBox.d.ts.map +1 -1
  23. package/dist/components/InputBox.js +9 -3
  24. package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
  25. package/dist/components/MarketplaceAddForm.js +13 -6
  26. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  27. package/dist/components/MarketplaceDetail.js +8 -3
  28. package/dist/components/MessageBlockItem.js +2 -2
  29. package/dist/components/MessageList.d.ts +4 -1
  30. package/dist/components/MessageList.d.ts.map +1 -1
  31. package/dist/components/MessageList.js +15 -8
  32. package/dist/components/PluginDetail.d.ts.map +1 -1
  33. package/dist/components/PluginDetail.js +14 -3
  34. package/dist/components/PluginManagerShell.d.ts.map +1 -1
  35. package/dist/components/PluginManagerShell.js +3 -3
  36. package/dist/components/PluginManagerTypes.d.ts +2 -0
  37. package/dist/components/PluginManagerTypes.d.ts.map +1 -1
  38. package/dist/components/SessionSelector.d.ts.map +1 -1
  39. package/dist/components/SessionSelector.js +10 -13
  40. package/dist/components/StatusCommand.d.ts +6 -0
  41. package/dist/components/StatusCommand.d.ts.map +1 -0
  42. package/dist/components/StatusCommand.js +28 -0
  43. package/dist/components/WorktreeExitPrompt.d.ts +13 -0
  44. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -0
  45. package/dist/components/WorktreeExitPrompt.js +26 -0
  46. package/dist/contexts/useChat.d.ts +9 -5
  47. package/dist/contexts/useChat.d.ts.map +1 -1
  48. package/dist/contexts/useChat.js +38 -8
  49. package/dist/contracts/status.d.ts +8 -0
  50. package/dist/contracts/status.d.ts.map +1 -0
  51. package/dist/contracts/status.js +1 -0
  52. package/dist/hooks/useInputManager.d.ts +2 -0
  53. package/dist/hooks/useInputManager.d.ts.map +1 -1
  54. package/dist/hooks/useInputManager.js +12 -0
  55. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  56. package/dist/hooks/usePluginManager.js +41 -13
  57. package/dist/hooks/useTasks.js +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +53 -4
  60. package/dist/managers/InputManager.d.ts +6 -0
  61. package/dist/managers/InputManager.d.ts.map +1 -1
  62. package/dist/managers/InputManager.js +32 -13
  63. package/dist/print-cli.d.ts +2 -4
  64. package/dist/print-cli.d.ts.map +1 -1
  65. package/dist/print-cli.js +31 -2
  66. package/dist/session-selector-cli.d.ts +3 -1
  67. package/dist/session-selector-cli.d.ts.map +1 -1
  68. package/dist/session-selector-cli.js +2 -2
  69. package/dist/types.d.ts +11 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +1 -0
  72. package/dist/utils/worktree.d.ts +23 -0
  73. package/dist/utils/worktree.d.ts.map +1 -0
  74. package/dist/utils/worktree.js +135 -0
  75. package/package.json +2 -2
  76. package/src/cli.tsx +36 -59
  77. package/src/components/App.tsx +99 -11
  78. package/src/components/BackgroundTaskManager.tsx +12 -20
  79. package/src/components/{CommandOutputDisplay.tsx → BangDisplay.tsx} +4 -4
  80. package/src/components/ChatInterface.tsx +8 -0
  81. package/src/components/CommandSelector.tsx +18 -1
  82. package/src/components/ConfirmationSelector.tsx +118 -9
  83. package/src/components/HelpView.tsx +2 -0
  84. package/src/components/HistorySearch.tsx +25 -30
  85. package/src/components/InputBox.tsx +11 -1
  86. package/src/components/MarketplaceAddForm.tsx +21 -8
  87. package/src/components/MarketplaceDetail.tsx +19 -4
  88. package/src/components/MessageBlockItem.tsx +3 -3
  89. package/src/components/MessageList.tsx +47 -23
  90. package/src/components/PluginDetail.tsx +30 -6
  91. package/src/components/PluginManagerShell.tsx +24 -6
  92. package/src/components/PluginManagerTypes.ts +2 -0
  93. package/src/components/SessionSelector.tsx +38 -24
  94. package/src/components/StatusCommand.tsx +94 -0
  95. package/src/components/WorktreeExitPrompt.tsx +86 -0
  96. package/src/contexts/useChat.tsx +57 -13
  97. package/src/contracts/status.ts +7 -0
  98. package/src/hooks/useInputManager.ts +12 -0
  99. package/src/hooks/usePluginManager.ts +47 -13
  100. package/src/hooks/useTasks.ts +2 -2
  101. package/src/index.ts +71 -12
  102. package/src/managers/InputManager.ts +37 -15
  103. package/src/print-cli.ts +48 -5
  104. package/src/session-selector-cli.tsx +6 -2
  105. package/src/types.ts +11 -0
  106. package/src/utils/worktree.ts +164 -0
  107. package/dist/components/CommandOutputDisplay.d.ts +0 -9
  108. package/dist/components/CommandOutputDisplay.d.ts.map +0 -1
@@ -33,6 +33,7 @@ export interface InputManagerCallbacks {
33
33
  onMcpManagerStateChange?: (show: boolean) => void;
34
34
  onRewindManagerStateChange?: (show: boolean) => void;
35
35
  onHelpStateChange?: (show: boolean) => void;
36
+ onStatusCommandStateChange?: (show: boolean) => void;
36
37
  onImagesStateChange?: (images: AttachedImage[]) => void;
37
38
  onSendMessage?: (
38
39
  content: string,
@@ -40,6 +41,7 @@ export interface InputManagerCallbacks {
40
41
  ) => void | Promise<void>;
41
42
  onHasSlashCommand?: (commandId: string) => boolean;
42
43
  onAbortMessage?: () => void;
44
+ onClearMessages?: () => void;
43
45
  onBackgroundCurrentTask?: () => void;
44
46
  onResetHistoryNavigation?: () => void;
45
47
  onPermissionModeChange?: (mode: PermissionMode) => void;
@@ -86,6 +88,7 @@ export class InputManager {
86
88
  private showMcpManager: boolean = false;
87
89
  private showRewindManager: boolean = false;
88
90
  private showHelp: boolean = false;
91
+ private showStatusCommand: boolean = false;
89
92
 
90
93
  // Permission mode state
91
94
  private permissionMode: PermissionMode = "default";
@@ -342,7 +345,10 @@ export class InputManager {
342
345
 
343
346
  // If not an agent command or execution failed, check local commands
344
347
  if (!commandExecuted) {
345
- if (command === "tasks") {
348
+ if (command === "clear") {
349
+ this.callbacks.onClearMessages?.();
350
+ commandExecuted = true;
351
+ } else if (command === "tasks") {
346
352
  this.setShowBackgroundTaskManager(true);
347
353
  commandExecuted = true;
348
354
  } else if (command === "mcp") {
@@ -354,6 +360,9 @@ export class InputManager {
354
360
  } else if (command === "help") {
355
361
  this.setShowHelp(true);
356
362
  commandExecuted = true;
363
+ } else if (command === "status") {
364
+ this.setShowStatusCommand(true);
365
+ commandExecuted = true;
357
366
  }
358
367
  }
359
368
  })();
@@ -667,11 +676,33 @@ export class InputManager {
667
676
  this.callbacks.onHelpStateChange?.(show);
668
677
  }
669
678
 
679
+ getShowStatusCommand(): boolean {
680
+ return this.showStatusCommand;
681
+ }
682
+
683
+ setShowStatusCommand(show: boolean): void {
684
+ this.showStatusCommand = show;
685
+ this.callbacks.onStatusCommandStateChange?.(show);
686
+ }
687
+
670
688
  // Permission mode methods
671
689
  getPermissionMode(): PermissionMode {
672
690
  return this.permissionMode;
673
691
  }
674
692
 
693
+ isAnySelectorOrManagerActive(): boolean {
694
+ return (
695
+ this.showFileSelector ||
696
+ this.showCommandSelector ||
697
+ this.showHistorySearch ||
698
+ this.showBackgroundTaskManager ||
699
+ this.showMcpManager ||
700
+ this.showRewindManager ||
701
+ this.showHelp ||
702
+ this.showStatusCommand
703
+ );
704
+ }
705
+
675
706
  setPermissionMode(mode: PermissionMode): void {
676
707
  this.permissionMode = mode;
677
708
  }
@@ -920,9 +951,7 @@ export class InputManager {
920
951
  if (
921
952
  key.escape &&
922
953
  (isLoading || isCommandRunning) &&
923
- !this.showBackgroundTaskManager &&
924
- !this.showMcpManager &&
925
- !this.showRewindManager
954
+ !this.isAnySelectorOrManagerActive()
926
955
  ) {
927
956
  // Unified interrupt for AI message generation and command execution
928
957
  this.callbacks.onAbortMessage?.();
@@ -937,22 +966,15 @@ export class InputManager {
937
966
  }
938
967
 
939
968
  // Check if any selector is active
940
- if (
941
- this.showFileSelector ||
942
- this.showCommandSelector ||
943
- this.showHistorySearch ||
944
- this.showBackgroundTaskManager ||
945
- this.showMcpManager ||
946
- this.showRewindManager ||
947
- this.showHelp
948
- ) {
969
+ if (this.isAnySelectorOrManagerActive()) {
949
970
  if (
950
971
  this.showBackgroundTaskManager ||
951
972
  this.showMcpManager ||
952
973
  this.showRewindManager ||
953
- this.showHelp
974
+ this.showHelp ||
975
+ this.showStatusCommand
954
976
  ) {
955
- // Task manager, MCP manager, Rewind and Help don't need to handle input, handled by component itself
977
+ // Task manager, MCP manager, Rewind, Help and Status don't need to handle input, handled by component itself
956
978
  // Return true to indicate we've "handled" it (by ignoring it) so it doesn't leak to normal input
957
979
  return true;
958
980
  }
package/src/print-cli.ts CHANGED
@@ -1,14 +1,19 @@
1
- import { Agent, AgentCallbacks } from "wave-agent-sdk";
1
+ import {
2
+ Agent,
3
+ AgentCallbacks,
4
+ hasUncommittedChanges,
5
+ hasNewCommits,
6
+ getDefaultRemoteBranch,
7
+ } from "wave-agent-sdk";
2
8
  import { displayUsageSummary } from "./utils/usageSummary.js";
9
+ import { removeWorktree } from "./utils/worktree.js";
10
+ import { BaseAppProps } from "./types.js";
3
11
 
4
- export interface PrintCliOptions {
12
+ export interface PrintCliOptions extends BaseAppProps {
5
13
  restoreSessionId?: string;
6
14
  continueLastSession?: boolean;
7
15
  message?: string;
8
16
  showStats?: boolean;
9
- bypassPermissions?: boolean;
10
- pluginDirs?: string[];
11
- tools?: string[];
12
17
  }
13
18
 
14
19
  function displayTimingInfo(startTime: Date, showStats: boolean): void {
@@ -35,6 +40,9 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
35
40
  bypassPermissions,
36
41
  pluginDirs,
37
42
  tools,
43
+ worktreeSession,
44
+ workdir,
45
+ model,
38
46
  } = options;
39
47
 
40
48
  if (
@@ -139,6 +147,8 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
139
147
  permissionMode: bypassPermissions ? "bypassPermissions" : undefined,
140
148
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
141
149
  tools,
150
+ workdir,
151
+ model,
142
152
  // 保持流式模式以获得更好的命令行用户体验
143
153
  });
144
154
 
@@ -163,6 +173,23 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
163
173
 
164
174
  // Destroy agent and exit after sendMessage completes
165
175
  await agent.destroy();
176
+
177
+ // Handle worktree cleanup for print mode
178
+ if (worktreeSession) {
179
+ const cwd = workdir || worktreeSession.path;
180
+ const baseBranch = getDefaultRemoteBranch(cwd);
181
+ const hasChanges = hasUncommittedChanges(cwd);
182
+ const hasCommits = hasNewCommits(cwd, baseBranch);
183
+
184
+ if (!hasChanges && !hasCommits) {
185
+ removeWorktree(worktreeSession);
186
+ } else {
187
+ process.stdout.write(
188
+ `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
189
+ );
190
+ }
191
+ }
192
+
166
193
  process.exit(0);
167
194
  } catch (error) {
168
195
  console.error("Failed to send message:", error);
@@ -182,6 +209,22 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
182
209
  displayTimingInfo(startTime, showStats);
183
210
 
184
211
  await agent.destroy();
212
+
213
+ // Handle worktree cleanup for print mode even on error
214
+ if (worktreeSession) {
215
+ const cwd = workdir || worktreeSession.path;
216
+ const baseBranch = getDefaultRemoteBranch(cwd);
217
+ const hasChanges = hasUncommittedChanges(cwd);
218
+ const hasCommits = hasNewCommits(cwd, baseBranch);
219
+
220
+ if (!hasChanges && !hasCommits) {
221
+ removeWorktree(worktreeSession);
222
+ } else {
223
+ process.stdout.write(
224
+ `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
225
+ );
226
+ }
227
+ }
185
228
  }
186
229
  process.exit(1);
187
230
  }
@@ -3,8 +3,12 @@ import { render, Box } from "ink";
3
3
  import { listSessions, truncateContent } from "wave-agent-sdk";
4
4
  import { SessionSelector } from "./components/SessionSelector.js";
5
5
 
6
- export async function startSessionSelectorCli(): Promise<string | null> {
7
- const currentWorkdir = process.cwd();
6
+ export async function startSessionSelectorCli({
7
+ workdir,
8
+ }: {
9
+ workdir?: string;
10
+ } = {}): Promise<string | null> {
11
+ const currentWorkdir = workdir || process.cwd();
8
12
  const sessions = await listSessions(currentWorkdir);
9
13
 
10
14
  if (sessions.length === 0) {
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { WorktreeSession } from "./utils/worktree.js";
2
+
3
+ export interface BaseAppProps {
4
+ bypassPermissions?: boolean;
5
+ pluginDirs?: string[];
6
+ tools?: string[];
7
+ worktreeSession?: WorktreeSession;
8
+ workdir?: string;
9
+ version?: string;
10
+ model?: string;
11
+ }
@@ -0,0 +1,164 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as path from "node:path";
3
+ import * as fs from "node:fs";
4
+ import { getGitRepoRoot, getDefaultRemoteBranch } from "wave-agent-sdk";
5
+
6
+ export interface WorktreeSession {
7
+ name: string;
8
+ path: string;
9
+ branch: string;
10
+ repoRoot: string;
11
+ hasUncommittedChanges: boolean;
12
+ hasNewCommits: boolean;
13
+ isNew: boolean;
14
+ }
15
+
16
+ export const WORKTREE_DIR = ".wave/worktrees";
17
+
18
+ /**
19
+ * Create a new git worktree
20
+ * @param name Worktree name
21
+ * @param cwd Current working directory
22
+ * @returns Worktree session details
23
+ */
24
+ export function createWorktree(name: string, cwd: string): WorktreeSession {
25
+ const repoRoot = getGitRepoRoot(cwd);
26
+ const worktreePath = path.join(repoRoot, WORKTREE_DIR, name);
27
+ const branchName = `worktree-${name}`;
28
+ const baseBranch = getDefaultRemoteBranch(cwd);
29
+
30
+ // Ensure parent directory exists
31
+ const parentDir = path.dirname(worktreePath);
32
+ if (!fs.existsSync(parentDir)) {
33
+ fs.mkdirSync(parentDir, { recursive: true });
34
+ }
35
+
36
+ // Check if worktree already exists
37
+ if (fs.existsSync(worktreePath)) {
38
+ // If it exists, we assume it's already set up correctly
39
+ return {
40
+ name,
41
+ path: worktreePath,
42
+ branch: branchName,
43
+ repoRoot,
44
+ hasUncommittedChanges: false,
45
+ hasNewCommits: false,
46
+ isNew: false,
47
+ };
48
+ }
49
+
50
+ try {
51
+ // Create worktree and branch
52
+ execSync(
53
+ `git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`,
54
+ {
55
+ cwd: repoRoot,
56
+ stdio: ["ignore", "pipe", "pipe"],
57
+ },
58
+ );
59
+
60
+ return {
61
+ name,
62
+ path: worktreePath,
63
+ branch: branchName,
64
+ repoRoot,
65
+ hasUncommittedChanges: false,
66
+ hasNewCommits: false,
67
+ isNew: true,
68
+ };
69
+ } catch (error: unknown) {
70
+ const stderr = (error as { stderr?: Buffer }).stderr?.toString() || "";
71
+ if (stderr.includes("already exists")) {
72
+ // If branch already exists, try to add worktree without -b
73
+ try {
74
+ execSync(`git worktree add "${worktreePath}" ${branchName}`, {
75
+ cwd: repoRoot,
76
+ stdio: ["ignore", "pipe", "pipe"],
77
+ });
78
+ return {
79
+ name,
80
+ path: worktreePath,
81
+ branch: branchName,
82
+ repoRoot,
83
+ hasUncommittedChanges: false,
84
+ hasNewCommits: false,
85
+ isNew: true,
86
+ };
87
+ } catch (innerError: unknown) {
88
+ throw new Error(
89
+ `Failed to add existing worktree branch: ${(innerError as Error).message}`,
90
+ );
91
+ }
92
+ }
93
+ throw new Error(
94
+ `Failed to create worktree: ${(error as Error).message}\n${stderr}`,
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Remove a git worktree and its associated branch
101
+ * @param session Worktree session details
102
+ */
103
+ export function removeWorktree(session: WorktreeSession): void {
104
+ const repoRoot = session.repoRoot;
105
+
106
+ try {
107
+ // Get current branch in worktree before removing it
108
+ let currentBranch: string | undefined;
109
+ try {
110
+ currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`, {
111
+ cwd: session.path,
112
+ encoding: "utf8",
113
+ stdio: ["ignore", "pipe", "ignore"],
114
+ }).trim();
115
+ } catch {
116
+ // Ignore errors getting current branch
117
+ }
118
+
119
+ // Remove worktree
120
+ execSync(`git worktree remove --force "${session.path}"`, {
121
+ cwd: repoRoot,
122
+ stdio: ["ignore", "pipe", "pipe"],
123
+ });
124
+
125
+ // Delete original branch
126
+ try {
127
+ execSync(`git branch -D ${session.branch}`, {
128
+ cwd: repoRoot,
129
+ stdio: ["ignore", "pipe", "pipe"],
130
+ });
131
+ } catch {
132
+ // Ignore errors deleting original branch
133
+ }
134
+
135
+ // Delete current branch if it's different and not a protected branch
136
+ if (
137
+ currentBranch &&
138
+ currentBranch !== session.branch &&
139
+ currentBranch !== "HEAD"
140
+ ) {
141
+ const defaultRemoteBranch = getDefaultRemoteBranch(repoRoot);
142
+ const defaultBranchName = defaultRemoteBranch.split("/").pop();
143
+
144
+ if (
145
+ currentBranch !== defaultBranchName &&
146
+ currentBranch !== "main" &&
147
+ currentBranch !== "master"
148
+ ) {
149
+ try {
150
+ execSync(`git branch -D ${currentBranch}`, {
151
+ cwd: repoRoot,
152
+ stdio: ["ignore", "pipe", "pipe"],
153
+ });
154
+ } catch {
155
+ // Ignore errors deleting current branch
156
+ }
157
+ }
158
+ }
159
+ } catch (error: unknown) {
160
+ console.error(
161
+ `Failed to remove worktree or branch: ${(error as Error).message}`,
162
+ );
163
+ }
164
+ }
@@ -1,9 +0,0 @@
1
- import React from "react";
2
- import type { CommandOutputBlock } from "wave-agent-sdk";
3
- interface CommandOutputDisplayProps {
4
- block: CommandOutputBlock;
5
- isExpanded?: boolean;
6
- }
7
- export declare const CommandOutputDisplay: React.FC<CommandOutputDisplayProps>;
8
- export {};
9
- //# sourceMappingURL=CommandOutputDisplay.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CommandOutputDisplay.d.ts","sourceRoot":"","sources":["../../src/components/CommandOutputDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,UAAU,yBAAyB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAiDpE,CAAC"}