wave-code 1.0.0 → 1.0.1

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 (70) hide show
  1. package/dist/cli.js +20 -1
  2. package/dist/components/App.js +7 -0
  3. package/dist/components/BtwDisplay.js +13 -3
  4. package/dist/components/ChatInterface.js +21 -6
  5. package/dist/components/InputBox.d.ts +0 -3
  6. package/dist/components/InputBox.js +11 -9
  7. package/dist/components/LoadingIndicator.d.ts +1 -2
  8. package/dist/components/LoadingIndicator.js +2 -2
  9. package/dist/components/Markdown.js +13 -16
  10. package/dist/components/MessageList.d.ts +2 -1
  11. package/dist/components/MessageList.js +2 -2
  12. package/dist/components/StatusLine.d.ts +0 -2
  13. package/dist/components/StatusLine.js +6 -6
  14. package/dist/components/TaskList.js +2 -1
  15. package/dist/components/ToolDisplay.d.ts +1 -0
  16. package/dist/components/ToolDisplay.js +17 -9
  17. package/dist/constants/commands.js +0 -6
  18. package/dist/contexts/useChat.d.ts +3 -5
  19. package/dist/contexts/useChat.js +242 -82
  20. package/dist/daemon-cli.d.ts +10 -0
  21. package/dist/daemon-cli.js +15 -0
  22. package/dist/hooks/useInputManager.js +99 -22
  23. package/dist/index.js +10 -0
  24. package/dist/managers/inputHandlers.js +50 -22
  25. package/dist/managers/inputReducer.d.ts +12 -2
  26. package/dist/managers/inputReducer.js +57 -9
  27. package/dist/stdio/agentBridge.d.ts +23 -0
  28. package/dist/stdio/agentBridge.js +126 -16
  29. package/dist/stdio/daemonServer.d.ts +67 -0
  30. package/dist/stdio/daemonServer.js +191 -0
  31. package/dist/stdio/index.d.ts +2 -0
  32. package/dist/stdio/index.js +2 -0
  33. package/dist/stdio/jsonRpcConnection.d.ts +30 -0
  34. package/dist/stdio/jsonRpcConnection.js +127 -0
  35. package/dist/stdio/protocol.d.ts +2 -2
  36. package/dist/stdio/stdioServer.d.ts +2 -7
  37. package/dist/stdio/stdioServer.js +9 -100
  38. package/dist/utils/bracketedPaste.d.ts +39 -0
  39. package/dist/utils/bracketedPaste.js +122 -0
  40. package/dist/utils/markdownTable.d.ts +34 -0
  41. package/dist/utils/markdownTable.js +302 -0
  42. package/dist/utils/throttle.d.ts +3 -3
  43. package/package.json +4 -2
  44. package/src/cli.tsx +20 -1
  45. package/src/components/App.tsx +5 -0
  46. package/src/components/BtwDisplay.tsx +36 -12
  47. package/src/components/ChatInterface.tsx +25 -12
  48. package/src/components/InputBox.tsx +10 -18
  49. package/src/components/LoadingIndicator.tsx +1 -4
  50. package/src/components/Markdown.tsx +15 -18
  51. package/src/components/MessageList.tsx +6 -0
  52. package/src/components/StatusLine.tsx +0 -10
  53. package/src/components/TaskList.tsx +2 -1
  54. package/src/components/ToolDisplay.tsx +17 -6
  55. package/src/constants/commands.ts +0 -6
  56. package/src/contexts/useChat.tsx +310 -95
  57. package/src/daemon-cli.ts +17 -0
  58. package/src/hooks/useInputManager.ts +108 -22
  59. package/src/index.ts +12 -0
  60. package/src/managers/inputHandlers.ts +49 -22
  61. package/src/managers/inputReducer.ts +66 -11
  62. package/src/stdio/agentBridge.ts +188 -17
  63. package/src/stdio/daemonServer.ts +212 -0
  64. package/src/stdio/index.ts +2 -0
  65. package/src/stdio/jsonRpcConnection.ts +160 -0
  66. package/src/stdio/protocol.ts +5 -2
  67. package/src/stdio/stdioServer.ts +14 -120
  68. package/src/utils/bracketedPaste.ts +170 -0
  69. package/src/utils/markdownTable.ts +359 -0
  70. package/src/utils/throttle.ts +8 -8
@@ -1,6 +1,7 @@
1
- import React, { useState, useRef, useEffect } from "react";
1
+ import React, { useState, useRef, useEffect, useCallback } from "react";
2
2
  import { Box, useStdout, measureElement, Static } from "ink";
3
3
  import type { DOMElement } from "ink";
4
+ import { authService } from "wave-agent-sdk";
4
5
  import { MessageList } from "./MessageList.js";
5
6
  import { InputBox } from "./InputBox.js";
6
7
  import { LoadingIndicator } from "./LoadingIndicator.js";
@@ -37,9 +38,7 @@ export const ChatInterface: React.FC = () => {
37
38
  workdir,
38
39
  remountKey,
39
40
  requestRemount,
40
- isGoalActive,
41
- goalElapsed,
42
- isGoalEvaluating,
41
+ getGatewayConfig,
43
42
  } = useChat();
44
43
 
45
44
  const displayMessages = messages;
@@ -49,6 +48,26 @@ export const ChatInterface: React.FC = () => {
49
48
  const terminalHeight = stdout?.rows ?? 24;
50
49
  const chatInterfaceRef = useRef<DOMElement>(null);
51
50
 
51
+ // Compute whether the user has any usable auth/direct-API config,
52
+ // so the welcome page can prompt /login when neither is present.
53
+ const computeAuthState = useCallback((): boolean => {
54
+ if (authService.isSSOAuthenticated()) return true;
55
+ const gateway = getGatewayConfig();
56
+ return Boolean(gateway.apiKey || gateway.baseURL);
57
+ }, [getGatewayConfig]);
58
+
59
+ const [hasAuth, setHasAuth] = useState<boolean>(computeAuthState);
60
+
61
+ // Keep the /login hint in sync with auth state changes (login/logout).
62
+ useEffect(() => {
63
+ const unsubscribe = authService.onAuthChange(() => {
64
+ setHasAuth(computeAuthState());
65
+ });
66
+ return unsubscribe;
67
+ }, [computeAuthState]);
68
+
69
+ const showLoginHint = !hasAuth;
70
+
52
71
  // Handle forceStatic mode for overflow and request remount when exiting
53
72
  useEffect(() => {
54
73
  if (isConfirmationVisible && chatInterfaceRef.current) {
@@ -79,19 +98,16 @@ export const ChatInterface: React.FC = () => {
79
98
  version={version}
80
99
  workdir={workdir}
81
100
  forceStatic={forceStatic}
101
+ showLoginHint={showLoginHint}
82
102
  />
83
103
 
84
104
  {!isConfirmationVisible && !isExpanded && (
85
105
  <>
86
- {(isLoading ||
87
- isCommandRunning ||
88
- isCompacting ||
89
- isGoalEvaluating) && (
106
+ {(isLoading || isCommandRunning || isCompacting) && (
90
107
  <LoadingIndicator
91
108
  isLoading={isLoading}
92
109
  isCommandRunning={isCommandRunning}
93
110
  isCompacting={isCompacting}
94
- isGoalEvaluating={isGoalEvaluating}
95
111
  latestTotalTokens={latestTotalTokens}
96
112
  />
97
113
  )}
@@ -101,7 +117,6 @@ export const ChatInterface: React.FC = () => {
101
117
  isLoading={isLoading}
102
118
  isCommandRunning={isCommandRunning}
103
119
  isCompacting={isCompacting}
104
- isGoalEvaluating={isGoalEvaluating}
105
120
  sendMessage={sendMessage}
106
121
  abortMessage={abortMessage}
107
122
  mcpServers={mcpServers}
@@ -111,8 +126,6 @@ export const ChatInterface: React.FC = () => {
111
126
  hasSlashCommand={hasSlashCommand}
112
127
  latestTotalTokens={latestTotalTokens}
113
128
  maxInputTokens={maxInputTokens}
114
- isGoalActive={isGoalActive}
115
- goalElapsed={goalElapsed}
116
129
  />
117
130
  </>
118
131
  )}
@@ -32,7 +32,6 @@ export interface InputBoxProps {
32
32
  isLoading?: boolean;
33
33
  isCommandRunning?: boolean;
34
34
  isCompacting?: boolean;
35
- isGoalEvaluating?: boolean;
36
35
  workdir?: string;
37
36
  sendMessage?: (
38
37
  message: string,
@@ -50,16 +49,12 @@ export interface InputBoxProps {
50
49
  // Token usage
51
50
  latestTotalTokens?: number;
52
51
  maxInputTokens?: number;
53
- // Goal state
54
- isGoalActive?: boolean;
55
- goalElapsed?: string;
56
52
  }
57
53
 
58
54
  export const InputBox: React.FC<InputBoxProps> = ({
59
55
  isLoading,
60
56
  isCommandRunning,
61
57
  isCompacting,
62
- isGoalEvaluating,
63
58
  sendMessage = () => {},
64
59
  abortMessage = () => {},
65
60
  mcpServers = [],
@@ -69,8 +64,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
69
64
  hasSlashCommand = () => false,
70
65
  latestTotalTokens = 0,
71
66
  maxInputTokens = 200000,
72
- isGoalActive,
73
- goalElapsed,
74
67
  }) => {
75
68
  const {
76
69
  permissionMode: chatPermissionMode,
@@ -84,13 +77,13 @@ export const InputBox: React.FC<InputBoxProps> = ({
84
77
  askBtw,
85
78
  clearMessages,
86
79
  compact,
87
- goalCommand,
88
80
  currentModel,
89
81
  configuredModels,
90
82
  setModel,
91
83
  recreateAgent,
92
84
  recallQueuedMessage,
93
85
  queuedMessages,
86
+ setIsBtwActive,
94
87
  } = useChat();
95
88
 
96
89
  // Ref to hold setInputText so queue callbacks can access it before useInputManager returns
@@ -100,12 +93,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
100
93
 
101
94
  // Idle means no AI work in flight. Esc double-press clear only applies when
102
95
  // idle; while busy, Esc keeps its abort semantics.
103
- const isIdle = !(
104
- isLoading ||
105
- isCommandRunning ||
106
- isCompacting ||
107
- isGoalEvaluating
108
- );
96
+ const isIdle = !(isLoading || isCommandRunning || isCompacting);
109
97
 
110
98
  const onRecallQueuedMessage = useCallback(() => {
111
99
  const msg = recallQueuedMessage();
@@ -174,7 +162,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
174
162
  onAskBtw: askBtw,
175
163
  onClearMessages: clearMessages,
176
164
  onCompact: compact,
177
- onGoalCommand: goalCommand,
178
165
  onHasSlashCommand: hasSlashCommand,
179
166
  onAbortMessage: abortMessage,
180
167
  onBackgroundCurrentTask: backgroundCurrentTask,
@@ -192,6 +179,13 @@ export const InputBox: React.FC<InputBoxProps> = ({
192
179
  setInputTextRef.current = setInputText;
193
180
  }, [setInputText]);
194
181
 
182
+ // Sync the btw overlay's visibility to ChatContext so siblings (TaskList)
183
+ // can hide while the side-question is on display (aligned with Claude Code,
184
+ // which suppresses the expanded task list while a local-jsx command shows).
185
+ useEffect(() => {
186
+ setIsBtwActive(btwState.question !== "" || btwState.answer !== undefined);
187
+ }, [btwState.question, btwState.answer, setIsBtwActive]);
188
+
195
189
  // Sync permission mode from useChat to InputManager
196
190
  useEffect(() => {
197
191
  setPermissionMode(chatPermissionMode);
@@ -349,7 +343,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
349
343
  <WorkflowManager onCancel={() => setShowWorkflowManager(false)} />
350
344
  )}
351
345
 
352
- {btwState.question
346
+ {btwState.question || btwState.answer
353
347
  ? null
354
348
  : showBackgroundTaskManager ||
355
349
  showMcpManager ||
@@ -384,8 +378,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
384
378
  <StatusLine
385
379
  permissionMode={permissionMode}
386
380
  isShellCommand={isShellCommand}
387
- isGoalActive={isGoalActive}
388
- goalElapsed={goalElapsed}
389
381
  latestTotalTokens={latestTotalTokens}
390
382
  maxInputTokens={maxInputTokens}
391
383
  />
@@ -5,7 +5,6 @@ export interface LoadingIndicatorProps {
5
5
  isLoading?: boolean;
6
6
  isCommandRunning?: boolean;
7
7
  isCompacting?: boolean;
8
- isGoalEvaluating?: boolean;
9
8
  latestTotalTokens?: number;
10
9
  }
11
10
 
@@ -13,12 +12,11 @@ export const LoadingIndicator = ({
13
12
  isLoading = false,
14
13
  isCommandRunning = false,
15
14
  isCompacting = false,
16
- isGoalEvaluating = false,
17
15
  latestTotalTokens = 0,
18
16
  }: LoadingIndicatorProps) => {
19
17
  return (
20
18
  <Box flexDirection="column">
21
- {isLoading && !isCompacting && !isGoalEvaluating && (
19
+ {isLoading && !isCompacting && (
22
20
  <Box>
23
21
  <Text color="yellow">✻ AI is thinking... </Text>
24
22
  {latestTotalTokens > 0 && (
@@ -51,7 +49,6 @@ export const LoadingIndicator = ({
51
49
  {isCompacting && (
52
50
  <Text color="magenta">✻ Compacting message history...</Text>
53
51
  )}
54
- {isGoalEvaluating && <Text color="cyan">✻ Evaluating goal...</Text>}
55
52
  </Box>
56
53
  );
57
54
  };
@@ -1,8 +1,9 @@
1
1
  import React, { useMemo } from "react";
2
- import { Box, Text } from "ink";
2
+ import { Box, Text, useStdout } from "ink";
3
3
  import { Renderer, marked, type Tokens } from "marked";
4
4
  import chalk from "chalk";
5
5
  import { highlightToAnsi } from "../utils/highlightUtils.js";
6
+ import { renderMarkdownTable } from "../utils/markdownTable.js";
6
7
 
7
8
  export interface MarkdownProps {
8
9
  children: string;
@@ -19,6 +20,10 @@ const unescapeHtml = (html: string) => {
19
20
  };
20
21
 
21
22
  class AnsiRenderer extends Renderer<string> {
23
+ constructor(private readonly columns: number) {
24
+ super();
25
+ }
26
+
22
27
  override code({ text, lang }: Tokens.Code): string {
23
28
  const prefix = lang ? `\`\`\`${lang}` : "```";
24
29
  const suffix = "```";
@@ -82,20 +87,10 @@ class AnsiRenderer extends Renderer<string> {
82
87
  }
83
88
 
84
89
  override table(token: Tokens.Table): string {
85
- const header = token.header.map((cell) => this.tablecell(cell)).join("");
86
- const body = token.rows
87
- .map((row) => row.map((cell) => this.tablecell(cell)).join("") + "\n")
88
- .join("");
89
- return `\n${header}\n${body}\n`;
90
- }
91
-
92
- override tablerow({ text }: Tokens.TableRow): string {
93
- return text + "\n";
94
- }
95
-
96
- override tablecell(token: Tokens.TableCell): string {
97
- const text = token.header ? chalk.bold(token.text) : token.text;
98
- return text + " | ";
90
+ const table = renderMarkdownTable(token, this.columns, (tokens) =>
91
+ this.parser.parseInline(tokens),
92
+ );
93
+ return `\n${table}\n`;
99
94
  }
100
95
 
101
96
  override strong({ tokens }: Tokens.Strong): string {
@@ -140,17 +135,19 @@ class AnsiRenderer extends Renderer<string> {
140
135
  }
141
136
  }
142
137
 
143
- const renderer = new AnsiRenderer();
138
+ const createRenderer = (columns: number) => new AnsiRenderer(columns);
144
139
 
145
140
  // Markdown component using custom ANSI renderer
146
141
  export const Markdown = React.memo(({ children }: MarkdownProps) => {
142
+ const { stdout } = useStdout();
143
+ const columns = stdout?.columns ?? 80;
147
144
  const ansiContent = useMemo(() => {
148
145
  return marked.parse(children, {
149
- renderer,
146
+ renderer: createRenderer(columns),
150
147
  gfm: true,
151
148
  breaks: true,
152
149
  }) as string;
153
- }, [children]);
150
+ }, [children, columns]);
154
151
 
155
152
  return (
156
153
  <Box flexDirection="column">
@@ -13,6 +13,7 @@ export interface MessageListProps {
13
13
  forceStatic?: boolean;
14
14
  version?: string;
15
15
  workdir?: string;
16
+ showLoginHint?: boolean;
16
17
  }
17
18
 
18
19
  export const MessageList = React.memo(
@@ -22,6 +23,7 @@ export const MessageList = React.memo(
22
23
  forceStatic = false,
23
24
  version,
24
25
  workdir,
26
+ showLoginHint = false,
25
27
  }: MessageListProps) => {
26
28
  const maxMessages = isExpanded
27
29
  ? MAX_MESSAGES_EXPANDED
@@ -135,6 +137,10 @@ export const MessageList = React.memo(
135
137
  </Static>
136
138
  )}
137
139
 
140
+ {/* Login hint — rendered in the dynamic area (not Static) so it can
141
+ disappear in real time when the user authenticates. */}
142
+ {showLoginHint && <Text color="gray">Type /login to authenticate</Text>}
143
+
138
144
  {/* Dynamic blocks */}
139
145
  {dynamicBlocks.length > 0 && (
140
146
  <Box flexDirection="column">
@@ -4,8 +4,6 @@ import { Box, Text } from "ink";
4
4
  export interface StatusLineProps {
5
5
  permissionMode: string;
6
6
  isShellCommand: boolean;
7
- isGoalActive?: boolean;
8
- goalElapsed?: string;
9
7
  latestTotalTokens?: number;
10
8
  maxInputTokens?: number;
11
9
  }
@@ -13,8 +11,6 @@ export interface StatusLineProps {
13
11
  export const StatusLine: React.FC<StatusLineProps> = ({
14
12
  permissionMode,
15
13
  isShellCommand,
16
- isGoalActive,
17
- goalElapsed,
18
14
  latestTotalTokens = 0,
19
15
  maxInputTokens = 200000,
20
16
  }) => {
@@ -34,12 +30,6 @@ export const StatusLine: React.FC<StatusLineProps> = ({
34
30
  </Text>
35
31
  ) : (
36
32
  <Box>
37
- {isGoalActive && (
38
- <Text color="gray">
39
- <Text color="cyan">◎ /goal</Text> active{" "}
40
- {goalElapsed && <Text>({goalElapsed})</Text>} |{" "}
41
- </Text>
42
- )}
43
33
  <Text color="gray">
44
34
  Mode:{" "}
45
35
  <Text
@@ -91,7 +91,7 @@ function getDisplayLimit(rows: number | undefined): number {
91
91
 
92
92
  export const TaskList: React.FC = () => {
93
93
  const tasks = useTasks();
94
- const { isTaskListVisible } = useChat();
94
+ const { isTaskListVisible, isBtwActive } = useChat();
95
95
  const { stdout } = useStdout();
96
96
 
97
97
  const completionTimestampsRef = React.useRef<Map<string, number>>(new Map());
@@ -188,6 +188,7 @@ export const TaskList: React.FC = () => {
188
188
  if (
189
189
  tasks.length === 0 ||
190
190
  !isTaskListVisible ||
191
+ isBtwActive ||
191
192
  autoHidden ||
192
193
  (allCompleted && !hadIncompleteRef.current)
193
194
  ) {
@@ -9,6 +9,22 @@ interface ToolDisplayProps {
9
9
  isExpanded?: boolean;
10
10
  }
11
11
 
12
+ // Status dot color for a tool block. In-flight stages (start/streaming/running)
13
+ // never show red — the outcome isn't known until the tool reaches "end".
14
+ // An explicit error overrides everything.
15
+ export const getToolStatusColor = (
16
+ stage: ToolBlock["stage"],
17
+ success?: boolean,
18
+ error?: string | Error,
19
+ ): string => {
20
+ if (error) return "red";
21
+ if (stage === "start" || stage === "streaming") return "gray";
22
+ if (stage === "running") return "yellow";
23
+ if (success) return "green";
24
+ if (success === false) return "red";
25
+ return "gray"; // Unknown state or no state information
26
+ };
27
+
12
28
  export const ToolDisplay: React.FC<ToolDisplayProps> = ({
13
29
  block,
14
30
  isExpanded = false,
@@ -19,12 +35,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
19
35
  // Directly use compactParams
20
36
  // (no change needed as we destructured it above)
21
37
 
22
- const getStatusColor = () => {
23
- if (stage === "running") return "yellow";
24
- if (success) return "green";
25
- if (error || success === false) return "red";
26
- return "gray"; // Unknown state or no state information
27
- };
38
+ const getStatusColor = () => getToolStatusColor(stage, success, error);
28
39
 
29
40
  const hasImages = () => {
30
41
  return block.images && block.images.length > 0;
@@ -80,10 +80,4 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
80
80
  description: "Compact conversation history to reduce context usage",
81
81
  handler: () => {},
82
82
  },
83
- {
84
- id: "goal",
85
- name: "goal",
86
- description: "Set, check, or clear an autonomous goal for the session",
87
- handler: () => {},
88
- },
89
83
  ];