wave-code 0.17.1 → 0.17.2

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 (57) hide show
  1. package/dist/acp/agent.js +1 -1
  2. package/dist/components/ChatInterface.d.ts.map +1 -1
  3. package/dist/components/ChatInterface.js +5 -2
  4. package/dist/components/InputBox.d.ts +2 -0
  5. package/dist/components/InputBox.d.ts.map +1 -1
  6. package/dist/components/InputBox.js +29 -8
  7. package/dist/components/LoadingIndicator.d.ts +2 -1
  8. package/dist/components/LoadingIndicator.d.ts.map +1 -1
  9. package/dist/components/LoadingIndicator.js +2 -2
  10. package/dist/components/QueuedMessageList.d.ts.map +1 -1
  11. package/dist/components/QueuedMessageList.js +10 -10
  12. package/dist/components/StatusLine.d.ts +2 -0
  13. package/dist/components/StatusLine.d.ts.map +1 -1
  14. package/dist/components/StatusLine.js +6 -6
  15. package/dist/components/TaskList.d.ts +3 -0
  16. package/dist/components/TaskList.d.ts.map +1 -1
  17. package/dist/components/TaskList.js +171 -28
  18. package/dist/components/TaskNotificationMessage.d.ts.map +1 -1
  19. package/dist/components/TaskNotificationMessage.js +1 -0
  20. package/dist/components/WorkflowManager.d.ts +6 -0
  21. package/dist/components/WorkflowManager.d.ts.map +1 -0
  22. package/dist/components/WorkflowManager.js +102 -0
  23. package/dist/constants/commands.d.ts.map +1 -1
  24. package/dist/constants/commands.js +6 -0
  25. package/dist/contexts/useChat.d.ts +8 -1
  26. package/dist/contexts/useChat.d.ts.map +1 -1
  27. package/dist/contexts/useChat.js +68 -4
  28. package/dist/hooks/useInputManager.d.ts +2 -0
  29. package/dist/hooks/useInputManager.d.ts.map +1 -1
  30. package/dist/hooks/useInputManager.js +22 -13
  31. package/dist/managers/inputHandlers.d.ts.map +1 -1
  32. package/dist/managers/inputHandlers.js +6 -3
  33. package/dist/managers/inputReducer.d.ts +10 -0
  34. package/dist/managers/inputReducer.d.ts.map +1 -1
  35. package/dist/managers/inputReducer.js +41 -4
  36. package/dist/print-cli.d.ts.map +1 -1
  37. package/dist/print-cli.js +3 -31
  38. package/dist/reducers/workflowManagerReducer.d.ts +41 -0
  39. package/dist/reducers/workflowManagerReducer.d.ts.map +1 -0
  40. package/dist/reducers/workflowManagerReducer.js +100 -0
  41. package/package.json +2 -2
  42. package/src/acp/agent.ts +1 -1
  43. package/src/components/ChatInterface.tsx +10 -1
  44. package/src/components/InputBox.tsx +43 -3
  45. package/src/components/LoadingIndicator.tsx +4 -1
  46. package/src/components/QueuedMessageList.tsx +7 -3
  47. package/src/components/StatusLine.tsx +28 -16
  48. package/src/components/TaskList.tsx +213 -19
  49. package/src/components/TaskNotificationMessage.tsx +1 -0
  50. package/src/components/WorkflowManager.tsx +320 -0
  51. package/src/constants/commands.ts +6 -0
  52. package/src/contexts/useChat.tsx +83 -4
  53. package/src/hooks/useInputManager.ts +25 -13
  54. package/src/managers/inputHandlers.ts +5 -4
  55. package/src/managers/inputReducer.ts +54 -6
  56. package/src/print-cli.ts +3 -37
  57. package/src/reducers/workflowManagerReducer.ts +143 -0
@@ -1,4 +1,5 @@
1
1
  import { getAtSelectorPosition, getSlashSelectorPosition, getWordEnd, SELECTOR_TRIGGERS, getProjectedState, } from "../utils/inputUtils.js";
2
+ import { AVAILABLE_COMMANDS } from "../constants/commands.js";
2
3
  export const initialState = {
3
4
  inputText: "",
4
5
  cursorPosition: 0,
@@ -23,6 +24,7 @@ export const initialState = {
23
24
  showLoginCommand: false,
24
25
  showPluginManager: false,
25
26
  showModelSelector: false,
27
+ showWorkflowManager: false,
26
28
  permissionMode: "default",
27
29
  selectorJustUsed: false,
28
30
  isPasting: false,
@@ -214,6 +216,12 @@ export function inputReducer(state, action) {
214
216
  showModelSelector: action.payload,
215
217
  selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
216
218
  };
219
+ case "SET_SHOW_WORKFLOW_MANAGER":
220
+ return {
221
+ ...state,
222
+ showWorkflowManager: action.payload,
223
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
224
+ };
217
225
  case "SET_PERMISSION_MODE":
218
226
  return { ...state, permissionMode: action.payload };
219
227
  case "SET_SELECTOR_JUST_USED":
@@ -457,9 +465,7 @@ export function inputReducer(state, action) {
457
465
  return { ...state, pendingEffect: null };
458
466
  case "HANDLE_KEY": {
459
467
  const { input, key } = action.payload;
460
- if (state.selectorJustUsed) {
461
- return state;
462
- }
468
+ const hasQueuedMessages = action.payload.hasQueuedMessages ?? false;
463
469
  // 1. BTW State Handling
464
470
  if (state.btwState.isActive) {
465
471
  if (key.escape) {
@@ -540,7 +546,8 @@ export function inputReducer(state, action) {
540
546
  state.showStatusCommand ||
541
547
  state.showLoginCommand ||
542
548
  state.showPluginManager ||
543
- state.showModelSelector)) {
549
+ state.showModelSelector ||
550
+ state.showWorkflowManager)) {
544
551
  return { ...state, pendingEffect: { type: "ABORT_MESSAGE" } };
545
552
  }
546
553
  return state;
@@ -582,6 +589,13 @@ export function inputReducer(state, action) {
582
589
  if (key.upArrow &&
583
590
  !state.showFileSelector &&
584
591
  !state.showCommandSelector) {
592
+ // If queue has messages, recall from queue first (before history)
593
+ if (hasQueuedMessages) {
594
+ return {
595
+ ...state,
596
+ pendingEffect: { type: "RECALL_QUEUED_MESSAGE" },
597
+ };
598
+ }
585
599
  if (state.history.length === 0) {
586
600
  return { ...state, pendingEffect: { type: "FETCH_HISTORY" } };
587
601
  }
@@ -754,6 +768,29 @@ export function inputReducer(state, action) {
754
768
  pendingEffect: question ? { type: "ASK_BTW", question } : null,
755
769
  };
756
770
  }
771
+ // Check if the content is a CLI-internal slash command (help, tasks,
772
+ // etc.) that should be executed locally rather than sent as a message.
773
+ // Agent slash commands and unknown /commands always go to SEND_MESSAGE.
774
+ if (contentWithPlaceholders.startsWith("/")) {
775
+ const spaceIndex = contentWithPlaceholders.indexOf(" ");
776
+ const commandName = spaceIndex === -1
777
+ ? contentWithPlaceholders.substring(1)
778
+ : contentWithPlaceholders.substring(1, spaceIndex);
779
+ const isInternalCommand = AVAILABLE_COMMANDS.some((cmd) => cmd.id === commandName);
780
+ if (isInternalCommand) {
781
+ return {
782
+ ...state,
783
+ inputText: "",
784
+ cursorPosition: 0,
785
+ historyIndex: -1,
786
+ longTextMap: {},
787
+ pendingEffect: {
788
+ type: "EXECUTE_COMMAND",
789
+ command: commandName,
790
+ },
791
+ };
792
+ }
793
+ }
757
794
  return {
758
795
  ...state,
759
796
  inputText: "",
@@ -1 +1 @@
1
- {"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,eAAe,CAAC,CAAC;CACvE;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuN3E"}
1
+ {"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,eAAe,CAAC,CAAC;CACvE;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqL3E"}
package/dist/print-cli.js CHANGED
@@ -24,14 +24,14 @@ export async function startPrintCli(options) {
24
24
  let agent;
25
25
  let isReasoning = false;
26
26
  let isContent = false;
27
- const subagentReasoningStates = new Map();
28
- const subagentContentStates = new Map();
29
27
  // Setup callbacks for agent
28
+ // Print mode only outputs the main agent's response (matching Claude Code's
29
+ // behavior). Subagent output is internal — the main agent incorporates
30
+ // relevant results in its own response.
30
31
  const callbacks = {
31
32
  onAssistantMessageAdded: () => {
32
33
  isReasoning = false;
33
34
  isContent = false;
34
- // Assistant message started - no content to output yet
35
35
  },
36
36
  onAssistantReasoningUpdated: (chunk) => {
37
37
  if (!isReasoning) {
@@ -50,36 +50,8 @@ export async function startPrintCli(options) {
50
50
  }
51
51
  isContent = true;
52
52
  }
53
- // FR-001: Stream content updates for real-time display - output only the new chunk
54
53
  process.stdout.write(chunk);
55
54
  },
56
- // Subagent message callbacks
57
- onSubagentAssistantMessageAdded: (subagentId) => {
58
- subagentReasoningStates.set(subagentId, false);
59
- subagentContentStates.set(subagentId, false);
60
- },
61
- onSubagentAssistantReasoningUpdated: (subagentId, chunk) => {
62
- if (!subagentReasoningStates.get(subagentId)) {
63
- process.stdout.write("\n 💭 Reasoning: ");
64
- subagentReasoningStates.set(subagentId, true);
65
- }
66
- process.stdout.write(chunk);
67
- },
68
- onSubagentAssistantContentUpdated: (subagentId, chunk) => {
69
- if (!subagentContentStates.get(subagentId)) {
70
- if (subagentReasoningStates.get(subagentId)) {
71
- process.stdout.write("\n 📝 Response: ");
72
- }
73
- else {
74
- process.stdout.write("\n ");
75
- }
76
- subagentContentStates.set(subagentId, true);
77
- }
78
- process.stdout.write(chunk);
79
- },
80
- onSubagentUserMessageAdded: (_subagentId, params) => {
81
- process.stdout.write(`\n 👤 User: ${params.content}\n`);
82
- },
83
55
  // Tool block callback - display tool name when tool starts
84
56
  onToolBlockUpdated: (params) => {
85
57
  // Print tool name only during 'running' stage (happens once per tool call)
@@ -0,0 +1,41 @@
1
+ import { Key } from "ink";
2
+ import type { WorkflowRun } from "wave-agent-sdk";
3
+ export type PendingEffect = {
4
+ type: "CANCEL";
5
+ } | {
6
+ type: "STOP_RUN";
7
+ runId: string;
8
+ };
9
+ export interface WorkflowManagerState {
10
+ runs: WorkflowRun[];
11
+ selectedIndex: number;
12
+ viewMode: "list" | "detail";
13
+ detailRunId: string | null;
14
+ pendingEffect: PendingEffect | null;
15
+ }
16
+ export type WorkflowManagerAction = {
17
+ type: "SET_RUNS";
18
+ runs: WorkflowRun[];
19
+ } | {
20
+ type: "SELECT_INDEX";
21
+ index: number;
22
+ } | {
23
+ type: "MOVE_UP";
24
+ } | {
25
+ type: "MOVE_DOWN";
26
+ } | {
27
+ type: "SELECT_CURRENT";
28
+ } | {
29
+ type: "SET_VIEW_MODE";
30
+ mode: "list" | "detail";
31
+ } | {
32
+ type: "RESET_DETAIL";
33
+ } | {
34
+ type: "HANDLE_KEY";
35
+ input: string;
36
+ key: Key;
37
+ } | {
38
+ type: "CLEAR_PENDING_EFFECT";
39
+ };
40
+ export declare function workflowManagerReducer(state: WorkflowManagerState, action: WorkflowManagerAction): WorkflowManagerState;
41
+ //# sourceMappingURL=workflowManagerReducer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowManagerReducer.d.ts","sourceRoot":"","sources":["../../src/reducers/workflowManagerReducer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAErC,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE,qBAAqB,GAC5B,oBAAoB,CAiHtB"}
@@ -0,0 +1,100 @@
1
+ export function workflowManagerReducer(state, action) {
2
+ switch (action.type) {
3
+ case "SET_RUNS":
4
+ return { ...state, runs: action.runs };
5
+ case "SELECT_INDEX":
6
+ return { ...state, selectedIndex: action.index };
7
+ case "MOVE_UP":
8
+ return { ...state, selectedIndex: Math.max(0, state.selectedIndex - 1) };
9
+ case "MOVE_DOWN":
10
+ return {
11
+ ...state,
12
+ selectedIndex: Math.min(state.runs.length > 0 ? state.runs.length - 1 : 0, state.selectedIndex + 1),
13
+ };
14
+ case "SELECT_CURRENT":
15
+ if (state.runs.length > 0 && state.selectedIndex < state.runs.length) {
16
+ return {
17
+ ...state,
18
+ detailRunId: state.runs[state.selectedIndex].runId,
19
+ viewMode: "detail",
20
+ };
21
+ }
22
+ return state;
23
+ case "SET_VIEW_MODE":
24
+ return { ...state, viewMode: action.mode };
25
+ case "RESET_DETAIL":
26
+ return {
27
+ ...state,
28
+ viewMode: "list",
29
+ detailRunId: null,
30
+ };
31
+ case "HANDLE_KEY": {
32
+ const { input, key } = action;
33
+ if (state.viewMode === "list") {
34
+ if (key.return) {
35
+ if (state.runs.length > 0 &&
36
+ state.selectedIndex < state.runs.length) {
37
+ return {
38
+ ...state,
39
+ detailRunId: state.runs[state.selectedIndex].runId,
40
+ viewMode: "detail",
41
+ };
42
+ }
43
+ return state;
44
+ }
45
+ if (key.escape) {
46
+ return { ...state, pendingEffect: { type: "CANCEL" } };
47
+ }
48
+ if (key.upArrow) {
49
+ return {
50
+ ...state,
51
+ selectedIndex: Math.max(0, state.selectedIndex - 1),
52
+ };
53
+ }
54
+ if (key.downArrow) {
55
+ return {
56
+ ...state,
57
+ selectedIndex: Math.min(state.runs.length > 0 ? state.runs.length - 1 : 0, state.selectedIndex + 1),
58
+ };
59
+ }
60
+ if (input === "k") {
61
+ if (state.runs.length > 0 &&
62
+ state.selectedIndex < state.runs.length) {
63
+ const selectedRun = state.runs[state.selectedIndex];
64
+ if (selectedRun.status === "running") {
65
+ return {
66
+ ...state,
67
+ pendingEffect: { type: "STOP_RUN", runId: selectedRun.runId },
68
+ };
69
+ }
70
+ }
71
+ return state;
72
+ }
73
+ }
74
+ else if (state.viewMode === "detail") {
75
+ if (key.escape) {
76
+ return {
77
+ ...state,
78
+ viewMode: "list",
79
+ detailRunId: null,
80
+ };
81
+ }
82
+ if (input === "k" && state.detailRunId) {
83
+ const run = state.runs.find((r) => r.runId === state.detailRunId);
84
+ if (run && run.status === "running") {
85
+ return {
86
+ ...state,
87
+ pendingEffect: { type: "STOP_RUN", runId: state.detailRunId },
88
+ };
89
+ }
90
+ return state;
91
+ }
92
+ }
93
+ return state;
94
+ }
95
+ case "CLEAR_PENDING_EFFECT":
96
+ return { ...state, pendingEffect: null };
97
+ default:
98
+ return state;
99
+ }
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
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.17.1"
45
+ "wave-agent-sdk": "0.17.2"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/react": "^19.1.8",
package/src/acp/agent.ts CHANGED
@@ -102,7 +102,7 @@ export class WaveAcpAgent implements AcpAgent {
102
102
 
103
103
  private getSessionConfigOptions(agent: WaveAgent): SessionConfigOption[] {
104
104
  const configuredModels = agent.getConfiguredModels();
105
- const currentModel = agent.getModelConfig().model;
105
+ const currentModel = agent.getModelConfig().model || "";
106
106
 
107
107
  return [
108
108
  {
@@ -37,6 +37,9 @@ export const ChatInterface: React.FC = () => {
37
37
  workdir,
38
38
  remountKey,
39
39
  requestRemount,
40
+ isGoalActive,
41
+ goalElapsed,
42
+ isGoalEvaluating,
40
43
  } = useChat();
41
44
 
42
45
  const displayMessages = messages;
@@ -80,11 +83,15 @@ export const ChatInterface: React.FC = () => {
80
83
 
81
84
  {!isConfirmationVisible && !isExpanded && (
82
85
  <>
83
- {(isLoading || isCommandRunning || isCompacting) && (
86
+ {(isLoading ||
87
+ isCommandRunning ||
88
+ isCompacting ||
89
+ isGoalEvaluating) && (
84
90
  <LoadingIndicator
85
91
  isLoading={isLoading}
86
92
  isCommandRunning={isCommandRunning}
87
93
  isCompacting={isCompacting}
94
+ isGoalEvaluating={isGoalEvaluating}
88
95
  latestTotalTokens={latestTotalTokens}
89
96
  />
90
97
  )}
@@ -102,6 +109,8 @@ export const ChatInterface: React.FC = () => {
102
109
  hasSlashCommand={hasSlashCommand}
103
110
  latestTotalTokens={latestTotalTokens}
104
111
  maxInputTokens={maxInputTokens}
112
+ isGoalActive={isGoalActive}
113
+ goalElapsed={goalElapsed}
105
114
  />
106
115
  </>
107
116
  )}
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from "react";
1
+ import React, { useEffect, useRef, useCallback } from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { useInput } from "ink";
4
4
  import { FileSelector } from "./FileSelector.js";
@@ -12,6 +12,7 @@ import { StatusCommand } from "./StatusCommand.js";
12
12
  import { LoginCommand } from "./LoginCommand.js";
13
13
  import { PluginManagerShell } from "./PluginManagerShell.js";
14
14
  import { ModelSelector } from "./ModelSelector.js";
15
+ import { WorkflowManager } from "./WorkflowManager.js";
15
16
  import { StatusLine } from "./StatusLine.js";
16
17
  import { BtwDisplay } from "./BtwDisplay.js";
17
18
  import { useInputManager } from "../hooks/useInputManager.js";
@@ -47,6 +48,9 @@ export interface InputBoxProps {
47
48
  // Token usage
48
49
  latestTotalTokens?: number;
49
50
  maxInputTokens?: number;
51
+ // Goal state
52
+ isGoalActive?: boolean;
53
+ goalElapsed?: string;
50
54
  }
51
55
 
52
56
  export const InputBox: React.FC<InputBoxProps> = ({
@@ -59,6 +63,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
59
63
  hasSlashCommand = () => false,
60
64
  latestTotalTokens = 0,
61
65
  maxInputTokens = 128000,
66
+ isGoalActive,
67
+ goalElapsed,
62
68
  }) => {
63
69
  const {
64
70
  permissionMode: chatPermissionMode,
@@ -74,8 +80,23 @@ export const InputBox: React.FC<InputBoxProps> = ({
74
80
  configuredModels,
75
81
  setModel,
76
82
  recreateAgent,
83
+ recallQueuedMessage,
84
+ queuedMessages,
77
85
  } = useChat();
78
86
 
87
+ // Ref to hold setInputText so queue callbacks can access it before useInputManager returns
88
+ const setInputTextRef = useRef<(text: string) => void>(() => {});
89
+
90
+ const hasQueuedMessages = (queuedMessages?.length ?? 0) > 0;
91
+
92
+ const onRecallQueuedMessage = useCallback(() => {
93
+ const msg = recallQueuedMessage();
94
+ if (msg) {
95
+ const prefix = msg.type === "bang" ? "!" : "";
96
+ setInputTextRef.current(prefix + msg.content);
97
+ }
98
+ }, [recallQueuedMessage]);
99
+
79
100
  // Input manager with all input state and functionality (including images)
80
101
  const {
81
102
  inputText,
@@ -110,6 +131,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
110
131
  showLoginCommand,
111
132
  showPluginManager,
112
133
  showModelSelector,
134
+ showWorkflowManager,
113
135
  setShowBackgroundTaskManager,
114
136
  setShowMcpManager,
115
137
  setShowRewindManager,
@@ -118,6 +140,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
118
140
  setShowLoginCommand,
119
141
  setShowPluginManager,
120
142
  setShowModelSelector,
143
+ setShowWorkflowManager,
121
144
  // Permission mode
122
145
  permissionMode,
123
146
  setPermissionMode,
@@ -127,6 +150,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
127
150
  handleInput,
128
151
  // Manager ready state
129
152
  isManagerReady,
153
+ // Direct state setters
154
+ setInputText,
130
155
  } = useInputManager({
131
156
  onSendMessage: sendMessage,
132
157
  onAskBtw: askBtw,
@@ -137,8 +162,15 @@ export const InputBox: React.FC<InputBoxProps> = ({
137
162
  sessionId,
138
163
  workdir: workingDirectory,
139
164
  getFullMessageThread,
165
+ hasQueuedMessages,
166
+ onRecallQueuedMessage,
140
167
  });
141
168
 
169
+ // Keep setInputText ref updated for queue callbacks
170
+ useEffect(() => {
171
+ setInputTextRef.current = setInputText;
172
+ }, [setInputText]);
173
+
142
174
  // Sync permission mode from useChat to InputManager
143
175
  useEffect(() => {
144
176
  setPermissionMode(chatPermissionMode);
@@ -157,7 +189,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
157
189
  showPluginManager ||
158
190
  showModelSelector ||
159
191
  showBackgroundTaskManager ||
160
- showMcpManager
192
+ showMcpManager ||
193
+ showWorkflowManager
161
194
  ) {
162
195
  return;
163
196
  }
@@ -291,13 +324,18 @@ export const InputBox: React.FC<InputBoxProps> = ({
291
324
  />
292
325
  )}
293
326
 
327
+ {showWorkflowManager && (
328
+ <WorkflowManager onCancel={() => setShowWorkflowManager(false)} />
329
+ )}
330
+
294
331
  {showBackgroundTaskManager ||
295
332
  showMcpManager ||
296
333
  showRewindManager ||
297
334
  showHelp ||
298
335
  showStatusCommand ||
299
336
  showLoginCommand ||
300
- showPluginManager || (
337
+ showPluginManager ||
338
+ showWorkflowManager || (
301
339
  <Box flexDirection="column">
302
340
  <Box
303
341
  borderStyle="single"
@@ -330,6 +368,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
330
368
  permissionMode={permissionMode}
331
369
  isShellCommand={isShellCommand}
332
370
  isBtwActive={btwState.isActive}
371
+ isGoalActive={isGoalActive}
372
+ goalElapsed={goalElapsed}
333
373
  latestTotalTokens={latestTotalTokens}
334
374
  maxInputTokens={maxInputTokens}
335
375
  />
@@ -5,6 +5,7 @@ export interface LoadingIndicatorProps {
5
5
  isLoading?: boolean;
6
6
  isCommandRunning?: boolean;
7
7
  isCompacting?: boolean;
8
+ isGoalEvaluating?: boolean;
8
9
  latestTotalTokens?: number;
9
10
  }
10
11
 
@@ -12,11 +13,12 @@ export const LoadingIndicator = ({
12
13
  isLoading = false,
13
14
  isCommandRunning = false,
14
15
  isCompacting = false,
16
+ isGoalEvaluating = false,
15
17
  latestTotalTokens = 0,
16
18
  }: LoadingIndicatorProps) => {
17
19
  return (
18
20
  <Box flexDirection="column">
19
- {isLoading && !isCompacting && (
21
+ {isLoading && !isCompacting && !isGoalEvaluating && (
20
22
  <Box>
21
23
  <Text color="yellow">✻ AI is thinking... </Text>
22
24
  {latestTotalTokens > 0 && (
@@ -49,6 +51,7 @@ export const LoadingIndicator = ({
49
51
  {isCompacting && (
50
52
  <Text color="magenta">✻ Compacting message history...</Text>
51
53
  )}
54
+ {isGoalEvaluating && <Text color="cyan">✻ Evaluating goal...</Text>}
52
55
  </Box>
53
56
  );
54
57
  };
@@ -18,15 +18,19 @@ export const QueuedMessageList: React.FC = () => {
18
18
  const displayText = prefix + (content || (hasImages ? "[Images]" : ""));
19
19
 
20
20
  return (
21
- <Box key={index}>
21
+ <Box key={msg.id ?? index}>
22
+ <Text color="gray">[{index + 1}] </Text>
22
23
  <Text color="gray" italic>
23
- {displayText.length > 60
24
- ? `${displayText.substring(0, 57)}...`
24
+ {displayText.length > 55
25
+ ? `${displayText.substring(0, 52)}...`
25
26
  : displayText}
26
27
  </Text>
27
28
  </Box>
28
29
  );
29
30
  })}
31
+ <Text color="gray" dimColor>
32
+ {" "}↑ recall
33
+ </Text>
30
34
  </Box>
31
35
  );
32
36
  };
@@ -5,6 +5,8 @@ export interface StatusLineProps {
5
5
  permissionMode: string;
6
6
  isShellCommand: boolean;
7
7
  isBtwActive: boolean;
8
+ isGoalActive?: boolean;
9
+ goalElapsed?: string;
8
10
  latestTotalTokens?: number;
9
11
  maxInputTokens?: number;
10
12
  }
@@ -13,6 +15,8 @@ export const StatusLine: React.FC<StatusLineProps> = ({
13
15
  permissionMode,
14
16
  isShellCommand,
15
17
  isBtwActive,
18
+ isGoalActive,
19
+ goalElapsed,
16
20
  latestTotalTokens = 0,
17
21
  maxInputTokens = 128000,
18
22
  }) => {
@@ -35,22 +39,30 @@ export const StatusLine: React.FC<StatusLineProps> = ({
35
39
  Shell: <Text color="yellow">Run shell command</Text>
36
40
  </Text>
37
41
  ) : (
38
- <Text color="gray">
39
- Mode:{" "}
40
- <Text
41
- color={
42
- permissionMode === "plan"
43
- ? "yellow"
44
- : permissionMode === "bypassPermissions"
45
- ? "red"
46
- : "cyan"
47
- }
48
- bold={permissionMode === "bypassPermissions"}
49
- >
50
- {permissionMode}
51
- </Text>{" "}
52
- (Shift+Tab to cycle)
53
- </Text>
42
+ <Box>
43
+ {isGoalActive && (
44
+ <Text color="gray">
45
+ <Text color="cyan">◎ /goal</Text> active{" "}
46
+ {goalElapsed && <Text>({goalElapsed})</Text>} |{" "}
47
+ </Text>
48
+ )}
49
+ <Text color="gray">
50
+ Mode:{" "}
51
+ <Text
52
+ color={
53
+ permissionMode === "plan"
54
+ ? "yellow"
55
+ : permissionMode === "bypassPermissions"
56
+ ? "red"
57
+ : "cyan"
58
+ }
59
+ bold={permissionMode === "bypassPermissions"}
60
+ >
61
+ {permissionMode}
62
+ </Text>{" "}
63
+ (Shift+Tab to cycle)
64
+ </Text>
65
+ </Box>
54
66
  )}
55
67
  {percentage > 0 && (
56
68
  <Text color={contextColor}>{percentage}% context</Text>