File size: 3,723 Bytes
3d700dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { useCallback } from "react";
import { useConversationStore } from "#/stores/conversation-store";
import { useHandlePlanClick } from "#/hooks/use-handle-plan-click";
import {
  useMainWebSocketStatus,
  useUnifiedWebSocketStatus,
} from "#/hooks/use-unified-websocket-status";
import { usePlanningAgentState } from "#/hooks/use-agent-state";
import { AgentState } from "#/types/agent-state";
import { PLAN_COMMAND, CODE_COMMAND } from "#/utils/constants";

const PLAN_PREFIX = `${PLAN_COMMAND} `;
const CODE_PREFIX = `${CODE_COMMAND} `;

/**
 * Intercepts "/plan [task]" and "/code [task]" and toggles the conversation's
 * mode like the Code/Plan button, instead of sending them as a chat message.
 * A bare command only switches mode; "<task>" is sent immediately (mode is
 * set synchronously first, so the send routes to the new mode — see
 * conversation-websocket-context.tsx's `getState().conversationMode` read),
 * or becomes the new planner's `initial_message` if it doesn't exist yet.
 *
 * Swallows the command while the relevant agent is running, a planner is
 * being created, or the socket it needs is disconnected — "/code" gates on
 * the main socket alone (not the main+planning merged status) so a momentary
 * planning reconnect can't swallow it, and "/plan <task>" also checks the
 * planner's own running state so it can't be routed into one still mid-run.
 */
export const usePlanModeInterceptor = (
  conversationId: string | null | undefined,
  curAgentState: AgentState,
  onSubmit: (message: string) => void,
) => {
  const setConversationMode = useConversationStore(
    (s) => s.setConversationMode,
  );
  const { handlePlanClick, hasPlanner, isCreatingConversation } =
    useHandlePlanClick();
  const isMainWebSocketConnected = useMainWebSocketStatus() === "OPEN";
  const isWebSocketConnected = useUnifiedWebSocketStatus() === "OPEN";
  const { isPlanningAgentRunning } = usePlanningAgentState();

  return useCallback(
    (message: string) => {
      const trimmed = message.trim();
      const isPlan =
        trimmed === PLAN_COMMAND || trimmed.startsWith(PLAN_PREFIX);
      const isCode =
        trimmed === CODE_COMMAND || trimmed.startsWith(CODE_PREFIX);
      if (!conversationId || (!isPlan && !isCode)) {
        onSubmit(message);
        return;
      }

      if (curAgentState === AgentState.RUNNING || isCreatingConversation) {
        return;
      }

      if (isPlan) {
        if (isPlanningAgentRunning || !isWebSocketConnected) {
          return;
        }
        const task = trimmed.slice(PLAN_COMMAND.length).trim();
        if (task && hasPlanner) {
          // Planner already exists: switch mode, then send normally — the
          // send path reads the just-set mode synchronously, so this routes
          // to the planner rather than the code agent.
          setConversationMode("plan");
          onSubmit(task);
        } else {
          handlePlanClick(undefined, task || undefined);
        }
      } else {
        if (!isMainWebSocketConnected) {
          return;
        }
        setConversationMode("code");
        const task = trimmed.slice(CODE_COMMAND.length).trim();
        if (task) {
          // The parent (code) conversation always already exists, so this
          // can always send immediately — same synchronous-mode-read routing
          // as the /plan-with-existing-planner case above.
          onSubmit(task);
        }
      }
    },
    [
      conversationId,
      curAgentState,
      hasPlanner,
      isCreatingConversation,
      isMainWebSocketConnected,
      isPlanningAgentRunning,
      isWebSocketConnected,
      onSubmit,
      handlePlanClick,
      setConversationMode,
    ],
  );
};