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
@@ -18,6 +18,7 @@ import type {
18
18
  PermissionDecision,
19
19
  PermissionMode,
20
20
  QueuedMessage,
21
+ WorkflowRun,
21
22
  } from "wave-agent-sdk";
22
23
  import {
23
24
  Agent,
@@ -53,6 +54,8 @@ export interface ChatContextType {
53
54
  ) => Promise<void>;
54
55
  askBtw: (question: string) => Promise<string>;
55
56
  abortMessage: () => void;
57
+ recallQueuedMessage: () => QueuedMessage | null;
58
+ removeQueuedMessageById: (id: string) => boolean;
56
59
  latestTotalTokens: number;
57
60
  maxInputTokens: number;
58
61
  // Model functionality
@@ -66,6 +69,9 @@ export interface ChatContextType {
66
69
  disconnectMcpServer: (serverName: string) => Promise<boolean>;
67
70
  // Background tasks
68
71
  backgroundTasks: BackgroundTask[];
72
+ // Workflow runs
73
+ workflowRuns: WorkflowRun[];
74
+ stopWorkflowRun: (runId: string) => void;
69
75
  // Tasks
70
76
  tasks: Task[];
71
77
  getBackgroundTaskOutput: (taskId: string) => {
@@ -122,6 +128,10 @@ export interface ChatContextType {
122
128
  recreateAgent: () => void;
123
129
  // Trigger WorktreeRemove hook BEFORE agent destruction
124
130
  triggerWorktreeRemoveHook: (worktreePath: string) => Promise<void>;
131
+ // Goal state
132
+ isGoalActive: boolean;
133
+ goalElapsed?: string;
134
+ isGoalEvaluating: boolean;
125
135
  }
126
136
 
127
137
  const ChatContext = createContext<ChatContextType | null>(null);
@@ -201,6 +211,28 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
201
211
  const [currentModel, setCurrentModelState] = useState("");
202
212
  const [configuredModels, setConfiguredModels] = useState<string[]>([]);
203
213
  const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
214
+ const [isGoalActive, setIsGoalActive] = useState(false);
215
+ const [goalElapsed, setGoalElapsed] = useState<string | undefined>();
216
+ const [isGoalEvaluating, setIsGoalEvaluating] = useState(false);
217
+ const goalStartedAt = useRef<number | null>(null);
218
+
219
+ // Update goal elapsed time every 30s while active
220
+ useEffect(() => {
221
+ if (!isGoalActive || goalStartedAt.current === null) return;
222
+ const formatElapsed = (ms: number): string => {
223
+ const minutes = Math.floor(ms / 60000);
224
+ if (minutes < 1) return "<1m";
225
+ if (minutes < 60) return `${minutes}m`;
226
+ const hours = Math.floor(minutes / 60);
227
+ const remainingMin = minutes % 60;
228
+ return `${hours}h${remainingMin}m`;
229
+ };
230
+ const update = () =>
231
+ setGoalElapsed(formatElapsed(Date.now() - goalStartedAt.current!));
232
+ update();
233
+ const timer = setInterval(update, 30_000);
234
+ return () => clearInterval(timer);
235
+ }, [isGoalActive]);
204
236
 
205
237
  // MCP State
206
238
  const [mcpServerStatuses, setMcpServerStatuses] = useState<McpServerStatus[]>(
@@ -209,6 +241,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
209
241
 
210
242
  // Background tasks state
211
243
  const [backgroundTasks, setBackgroundTasks] = useState<BackgroundTask[]>([]);
244
+ // Workflow runs state
245
+ const [workflowRuns, setWorkflowRuns] = useState<WorkflowRun[]>([]);
212
246
  // Tasks state
213
247
  const [tasks, setTasks] = useState<Task[]>([]);
214
248
 
@@ -344,11 +378,25 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
344
378
  onCompactionStateChange: (isCompactingState) => {
345
379
  setIsCompacting(isCompactingState);
346
380
  },
347
- onBackgroundTasksChange: (tasks) => {
381
+ onBackgroundTasksChange: async (tasks) => {
348
382
  setBackgroundTasks([...tasks]);
383
+ // Also refresh workflow runs since workflows are background tasks
384
+ const runs = await agentRef.current?.getWorkflowRuns();
385
+ if (runs) setWorkflowRuns([...runs]);
349
386
  },
350
- onTasksChange: (tasks) => {
351
- setTasks([...tasks]);
387
+ onTasksChange: (newTasks) => {
388
+ setTasks((prev) => {
389
+ if (
390
+ prev.length === newTasks.length &&
391
+ prev.every(
392
+ (t, i) =>
393
+ t.id === newTasks[i].id && t.status === newTasks[i].status,
394
+ )
395
+ ) {
396
+ return prev;
397
+ }
398
+ return [...newTasks];
399
+ });
352
400
  },
353
401
  onPermissionModeChange: (mode) => {
354
402
  setPermissionModeState(mode);
@@ -368,6 +416,18 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
368
416
  onQueuedMessagesChange: (messages) => {
369
417
  setQueuedMessages([...messages]);
370
418
  },
419
+ onGoalStateChange: (active, _condition, elapsed) => {
420
+ setIsGoalActive(active);
421
+ if (active) {
422
+ goalStartedAt.current = Date.now();
423
+ } else {
424
+ goalStartedAt.current = null;
425
+ }
426
+ setGoalElapsed(elapsed);
427
+ },
428
+ onGoalEvaluating: (evaluating) => {
429
+ setIsGoalEvaluating(evaluating);
430
+ },
371
431
  };
372
432
 
373
433
  try {
@@ -424,7 +484,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
424
484
  setIsCompacting(agent.isCompacting);
425
485
  setPermissionModeState(agent.getPermissionMode());
426
486
  setWorkingDirectory(agent.workingDirectory);
427
- setCurrentModelState(agent.getModelConfig().model);
487
+ setCurrentModelState(agent.getModelConfig().model || "");
428
488
  setConfiguredModels(agent.getConfiguredModels());
429
489
  setMaxInputTokens(agent.getMaxInputTokens());
430
490
 
@@ -568,6 +628,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
568
628
  agentRef.current?.abortMessage();
569
629
  }, []);
570
630
 
631
+ const recallQueuedMessage = useCallback((): QueuedMessage | null => {
632
+ return agentRef.current?.recallQueuedMessage() ?? null;
633
+ }, []);
634
+
635
+ const removeQueuedMessageById = useCallback((id: string): boolean => {
636
+ return agentRef.current?.removeQueuedMessageById(id) ?? false;
637
+ }, []);
638
+
571
639
  // Permission management methods
572
640
  const setPermissionMode = useCallback((mode: PermissionMode) => {
573
641
  setPermissionModeState((prev) => {
@@ -599,6 +667,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
599
667
  return agentRef.current.stopBackgroundTask(taskId);
600
668
  }, []);
601
669
 
670
+ const stopWorkflowRun = useCallback((runId: string) => {
671
+ agentRef.current?.stopWorkflowRun(runId);
672
+ }, []);
673
+
602
674
  const hasSlashCommand = useCallback((commandId: string) => {
603
675
  if (!agentRef.current) return false;
604
676
  return agentRef.current.hasSlashCommand(commandId);
@@ -753,6 +825,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
753
825
  sendMessage,
754
826
  askBtw,
755
827
  abortMessage,
828
+ recallQueuedMessage,
829
+ removeQueuedMessageById,
756
830
  latestTotalTokens,
757
831
  maxInputTokens,
758
832
  currentModel,
@@ -764,6 +838,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
764
838
  connectMcpServer,
765
839
  disconnectMcpServer,
766
840
  backgroundTasks,
841
+ workflowRuns,
842
+ stopWorkflowRun,
767
843
  tasks,
768
844
  getBackgroundTaskOutput,
769
845
  stopBackgroundTask,
@@ -791,6 +867,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
791
867
  workdir,
792
868
  recreateAgent,
793
869
  triggerWorktreeRemoveHook,
870
+ isGoalActive,
871
+ goalElapsed,
872
+ isGoalEvaluating,
794
873
  };
795
874
 
796
875
  return (
@@ -31,6 +31,7 @@ export const useInputManager = (
31
31
  onStatusCommandStateChange,
32
32
  onPluginManagerStateChange,
33
33
  onModelSelectorStateChange,
34
+ onWorkflowManagerStateChange,
34
35
  onImagesStateChange,
35
36
  onSendMessage,
36
37
  onHasSlashCommand,
@@ -42,18 +43,10 @@ export const useInputManager = (
42
43
  workdir,
43
44
  getFullMessageThread,
44
45
  logger,
46
+ hasQueuedMessages: hasQueuedMessagesProp,
47
+ onRecallQueuedMessage,
45
48
  } = callbacks;
46
49
 
47
- // Handle selectorJustUsed reset
48
- useEffect(() => {
49
- if (state.selectorJustUsed) {
50
- const timer = setTimeout(() => {
51
- dispatch({ type: "SET_SELECTOR_JUST_USED", payload: false });
52
- }, 0);
53
- return () => clearTimeout(timer);
54
- }
55
- }, [state.selectorJustUsed]);
56
-
57
50
  // Handle debounced file search
58
51
  useEffect(() => {
59
52
  if (state.showFileSelector) {
@@ -217,6 +210,8 @@ export const useInputManager = (
217
210
  dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
218
211
  } else if (command === "model") {
219
212
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
213
+ } else if (command === "workflows") {
214
+ dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: true });
220
215
  } else if (command === "btw") {
221
216
  dispatch({
222
217
  type: "SET_BTW_STATE",
@@ -225,6 +220,9 @@ export const useInputManager = (
225
220
  }
226
221
  }
227
222
  break;
223
+ case "RECALL_QUEUED_MESSAGE":
224
+ onRecallQueuedMessage?.();
225
+ break;
228
226
  }
229
227
  } catch (error) {
230
228
  console.error("Effect execution error:", error);
@@ -244,6 +242,7 @@ export const useInputManager = (
244
242
  getFullMessageThread,
245
243
  logger,
246
244
  onHasSlashCommand,
245
+ onRecallQueuedMessage,
247
246
  ]);
248
247
 
249
248
  useEffect(() => {
@@ -313,6 +312,10 @@ export const useInputManager = (
313
312
  onModelSelectorStateChange?.(state.showModelSelector);
314
313
  }, [state.showModelSelector, onModelSelectorStateChange]);
315
314
 
315
+ useEffect(() => {
316
+ onWorkflowManagerStateChange?.(state.showWorkflowManager);
317
+ }, [state.showWorkflowManager, onWorkflowManagerStateChange]);
318
+
316
319
  useEffect(() => {
317
320
  onImagesStateChange?.(state.attachedImages);
318
321
  }, [state.attachedImages, onImagesStateChange]);
@@ -479,6 +482,10 @@ export const useInputManager = (
479
482
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: show });
480
483
  }, []);
481
484
 
485
+ const setShowWorkflowManager = useCallback((show: boolean) => {
486
+ dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: show });
487
+ }, []);
488
+
482
489
  const setPermissionMode = useCallback(
483
490
  (mode: PermissionMode) => {
484
491
  dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
@@ -518,10 +525,11 @@ export const useInputManager = (
518
525
  input,
519
526
  key: {} as Key,
520
527
  hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
528
+ hasQueuedMessages: hasQueuedMessagesProp ?? false,
521
529
  },
522
530
  });
523
531
  },
524
- [onHasSlashCommand],
532
+ [onHasSlashCommand, hasQueuedMessagesProp],
525
533
  );
526
534
 
527
535
  const handleSubmit = useCallback(async () => {
@@ -531,9 +539,10 @@ export const useInputManager = (
531
539
  input: "",
532
540
  key: { return: true } as Key,
533
541
  hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
542
+ hasQueuedMessages: hasQueuedMessagesProp ?? false,
534
543
  },
535
544
  });
536
- }, [onHasSlashCommand]);
545
+ }, [onHasSlashCommand, hasQueuedMessagesProp]);
537
546
 
538
547
  const expandLongTextPlaceholders = useCallback(
539
548
  (text: string) => {
@@ -564,11 +573,12 @@ export const useInputManager = (
564
573
  input,
565
574
  key,
566
575
  hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
576
+ hasQueuedMessages: hasQueuedMessagesProp ?? false,
567
577
  },
568
578
  });
569
579
  return true;
570
580
  },
571
- [onHasSlashCommand],
581
+ [onHasSlashCommand, hasQueuedMessagesProp],
572
582
  );
573
583
 
574
584
  return {
@@ -593,6 +603,7 @@ export const useInputManager = (
593
603
  showLoginCommand: state.showLoginCommand,
594
604
  showPluginManager: state.showPluginManager,
595
605
  showModelSelector: state.showModelSelector,
606
+ showWorkflowManager: state.showWorkflowManager,
596
607
  permissionMode: state.permissionMode,
597
608
  attachedImages: state.attachedImages,
598
609
  btwState: state.btwState,
@@ -636,6 +647,7 @@ export const useInputManager = (
636
647
  setShowLoginCommand,
637
648
  setShowPluginManager,
638
649
  setShowModelSelector,
650
+ setShowWorkflowManager,
639
651
  setPermissionMode,
640
652
  setBtwState,
641
653
 
@@ -371,6 +371,8 @@ export const handleCommandSelect = (
371
371
  dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
372
372
  } else if (command === "model") {
373
373
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
374
+ } else if (command === "workflows") {
375
+ dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: true });
374
376
  } else if (command === "btw") {
375
377
  dispatch({
376
378
  type: "SET_BTW_STATE",
@@ -699,10 +701,6 @@ export const handleInput = async (
699
701
  key: Key,
700
702
  clearImages?: () => void,
701
703
  ): Promise<boolean> => {
702
- if (state.selectorJustUsed) {
703
- return true;
704
- }
705
-
706
704
  if (state.btwState.isActive) {
707
705
  if (key.escape) {
708
706
  dispatch({
@@ -756,6 +754,7 @@ export const handleInput = async (
756
754
  state.showStatusCommand ||
757
755
  state.showPluginManager ||
758
756
  state.showModelSelector ||
757
+ state.showWorkflowManager ||
759
758
  state.btwState.isActive
760
759
  )
761
760
  ) {
@@ -780,6 +779,7 @@ export const handleInput = async (
780
779
  state.showStatusCommand ||
781
780
  state.showPluginManager ||
782
781
  state.showModelSelector ||
782
+ state.showWorkflowManager ||
783
783
  state.btwState.isActive
784
784
  ) {
785
785
  if (
@@ -790,6 +790,7 @@ export const handleInput = async (
790
790
  state.showStatusCommand ||
791
791
  state.showPluginManager ||
792
792
  state.showModelSelector ||
793
+ state.showWorkflowManager ||
793
794
  state.btwState.isActive
794
795
  ) {
795
796
  return true;
@@ -13,6 +13,7 @@ import {
13
13
  SELECTOR_TRIGGERS,
14
14
  getProjectedState,
15
15
  } from "../utils/inputUtils.js";
16
+ import { AVAILABLE_COMMANDS } from "../constants/commands.js";
16
17
 
17
18
  export interface AttachedImage {
18
19
  id: number;
@@ -40,7 +41,8 @@ export type PendingEffect =
40
41
  | { type: "PERMISSION_MODE_CHANGE"; mode: PermissionMode }
41
42
  | { type: "FETCH_HISTORY" }
42
43
  | { type: "PASTE_IMAGE" }
43
- | { type: "EXECUTE_COMMAND"; command: string };
44
+ | { type: "EXECUTE_COMMAND"; command: string }
45
+ | { type: "RECALL_QUEUED_MESSAGE" };
44
46
 
45
47
  export interface InputManagerCallbacks {
46
48
  onInputTextChange?: (text: string) => void;
@@ -64,6 +66,7 @@ export interface InputManagerCallbacks {
64
66
  onStatusCommandStateChange?: (show: boolean) => void;
65
67
  onPluginManagerStateChange?: (show: boolean) => void;
66
68
  onModelSelectorStateChange?: (show: boolean) => void;
69
+ onWorkflowManagerStateChange?: (show: boolean) => void;
67
70
  onImagesStateChange?: (images: AttachedImage[]) => void;
68
71
  onSendMessage?: (
69
72
  content: string,
@@ -82,6 +85,8 @@ export interface InputManagerCallbacks {
82
85
  sessionIds: string[];
83
86
  }>;
84
87
  logger?: Logger;
88
+ hasQueuedMessages?: boolean;
89
+ onRecallQueuedMessage?: () => void;
85
90
  }
86
91
 
87
92
  export interface InputState {
@@ -108,6 +113,7 @@ export interface InputState {
108
113
  showLoginCommand: boolean;
109
114
  showPluginManager: boolean;
110
115
  showModelSelector: boolean;
116
+ showWorkflowManager: boolean;
111
117
  permissionMode: PermissionMode;
112
118
  selectorJustUsed: boolean;
113
119
  isPasting: boolean;
@@ -146,6 +152,7 @@ export const initialState: InputState = {
146
152
  showLoginCommand: false,
147
153
  showPluginManager: false,
148
154
  showModelSelector: false,
155
+ showWorkflowManager: false,
149
156
  permissionMode: "default",
150
157
  selectorJustUsed: false,
151
158
  isPasting: false,
@@ -191,6 +198,7 @@ export type InputAction =
191
198
  | { type: "SET_SHOW_LOGIN_COMMAND"; payload: boolean }
192
199
  | { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
193
200
  | { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
201
+ | { type: "SET_SHOW_WORKFLOW_MANAGER"; payload: boolean }
194
202
  | { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
195
203
  | { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
196
204
  | { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
@@ -222,6 +230,7 @@ export type InputAction =
222
230
  input: string;
223
231
  key: Key;
224
232
  hasSlashCommand: (cmd: string) => boolean;
233
+ hasQueuedMessages?: boolean;
225
234
  };
226
235
  };
227
236
 
@@ -413,6 +422,12 @@ export function inputReducer(
413
422
  showModelSelector: action.payload,
414
423
  selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
415
424
  };
425
+ case "SET_SHOW_WORKFLOW_MANAGER":
426
+ return {
427
+ ...state,
428
+ showWorkflowManager: action.payload,
429
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
430
+ };
416
431
  case "SET_PERMISSION_MODE":
417
432
  return { ...state, permissionMode: action.payload };
418
433
  case "SET_SELECTOR_JUST_USED":
@@ -671,10 +686,7 @@ export function inputReducer(
671
686
  return { ...state, pendingEffect: null };
672
687
  case "HANDLE_KEY": {
673
688
  const { input, key } = action.payload;
674
-
675
- if (state.selectorJustUsed) {
676
- return state;
677
- }
689
+ const hasQueuedMessages = action.payload.hasQueuedMessages ?? false;
678
690
 
679
691
  // 1. BTW State Handling
680
692
  if (state.btwState.isActive) {
@@ -760,7 +772,8 @@ export function inputReducer(
760
772
  state.showStatusCommand ||
761
773
  state.showLoginCommand ||
762
774
  state.showPluginManager ||
763
- state.showModelSelector
775
+ state.showModelSelector ||
776
+ state.showWorkflowManager
764
777
  )
765
778
  ) {
766
779
  return { ...state, pendingEffect: { type: "ABORT_MESSAGE" } };
@@ -812,6 +825,13 @@ export function inputReducer(
812
825
  !state.showFileSelector &&
813
826
  !state.showCommandSelector
814
827
  ) {
828
+ // If queue has messages, recall from queue first (before history)
829
+ if (hasQueuedMessages) {
830
+ return {
831
+ ...state,
832
+ pendingEffect: { type: "RECALL_QUEUED_MESSAGE" },
833
+ };
834
+ }
815
835
  if (state.history.length === 0) {
816
836
  return { ...state, pendingEffect: { type: "FETCH_HISTORY" } };
817
837
  }
@@ -1021,6 +1041,34 @@ export function inputReducer(
1021
1041
  };
1022
1042
  }
1023
1043
 
1044
+ // Check if the content is a CLI-internal slash command (help, tasks,
1045
+ // etc.) that should be executed locally rather than sent as a message.
1046
+ // Agent slash commands and unknown /commands always go to SEND_MESSAGE.
1047
+ if (contentWithPlaceholders.startsWith("/")) {
1048
+ const spaceIndex = contentWithPlaceholders.indexOf(" ");
1049
+ const commandName =
1050
+ spaceIndex === -1
1051
+ ? contentWithPlaceholders.substring(1)
1052
+ : contentWithPlaceholders.substring(1, spaceIndex);
1053
+
1054
+ const isInternalCommand = AVAILABLE_COMMANDS.some(
1055
+ (cmd) => cmd.id === commandName,
1056
+ );
1057
+ if (isInternalCommand) {
1058
+ return {
1059
+ ...state,
1060
+ inputText: "",
1061
+ cursorPosition: 0,
1062
+ historyIndex: -1,
1063
+ longTextMap: {},
1064
+ pendingEffect: {
1065
+ type: "EXECUTE_COMMAND",
1066
+ command: commandName,
1067
+ },
1068
+ };
1069
+ }
1070
+ }
1071
+
1024
1072
  return {
1025
1073
  ...state,
1026
1074
  inputText: "",
package/src/print-cli.ts CHANGED
@@ -65,15 +65,15 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
65
65
  let agent: Agent;
66
66
  let isReasoning = false;
67
67
  let isContent = false;
68
- const subagentReasoningStates = new Map<string, boolean>();
69
- const subagentContentStates = new Map<string, boolean>();
70
68
 
71
69
  // Setup callbacks for agent
70
+ // Print mode only outputs the main agent's response (matching Claude Code's
71
+ // behavior). Subagent output is internal — the main agent incorporates
72
+ // relevant results in its own response.
72
73
  const callbacks: AgentCallbacks = {
73
74
  onAssistantMessageAdded: () => {
74
75
  isReasoning = false;
75
76
  isContent = false;
76
- // Assistant message started - no content to output yet
77
77
  },
78
78
  onAssistantReasoningUpdated: (chunk: string) => {
79
79
  if (!isReasoning) {
@@ -91,43 +91,9 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
91
91
  }
92
92
  isContent = true;
93
93
  }
94
- // FR-001: Stream content updates for real-time display - output only the new chunk
95
94
  process.stdout.write(chunk);
96
95
  },
97
96
 
98
- // Subagent message callbacks
99
- onSubagentAssistantMessageAdded: (subagentId: string) => {
100
- subagentReasoningStates.set(subagentId, false);
101
- subagentContentStates.set(subagentId, false);
102
- },
103
- onSubagentAssistantReasoningUpdated: (
104
- subagentId: string,
105
- chunk: string,
106
- ) => {
107
- if (!subagentReasoningStates.get(subagentId)) {
108
- process.stdout.write("\n 💭 Reasoning: ");
109
- subagentReasoningStates.set(subagentId, true);
110
- }
111
- process.stdout.write(chunk);
112
- },
113
- onSubagentAssistantContentUpdated: (subagentId: string, chunk: string) => {
114
- if (!subagentContentStates.get(subagentId)) {
115
- if (subagentReasoningStates.get(subagentId)) {
116
- process.stdout.write("\n 📝 Response: ");
117
- } else {
118
- process.stdout.write("\n ");
119
- }
120
- subagentContentStates.set(subagentId, true);
121
- }
122
- process.stdout.write(chunk);
123
- },
124
- onSubagentUserMessageAdded: (
125
- _subagentId: string,
126
- params: { content: string },
127
- ) => {
128
- process.stdout.write(`\n 👤 User: ${params.content}\n`);
129
- },
130
-
131
97
  // Tool block callback - display tool name when tool starts
132
98
  onToolBlockUpdated: (params) => {
133
99
  // Print tool name only during 'running' stage (happens once per tool call)