@ibealec/create-zed-bridge 1.0.13 → 1.0.15

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.
@@ -32,16 +32,24 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ClaudeBridgeProvider: () => ClaudeBridgeProvider,
34
34
  ClaudeTerminal: () => ClaudeTerminal,
35
+ SessionPanel: () => SessionPanel,
36
+ SessionTabs: () => SessionTabs,
35
37
  TaskStatusToast: () => TaskStatusToast,
38
+ VoiceInput: () => VoiceInput,
36
39
  captureContext: () => captureContext,
37
40
  captureElementScreenshot: () => captureElementScreenshot,
38
41
  capturePageContext: () => capturePageContext,
39
- useClaudeBridge: () => useClaudeBridge
42
+ getSessionDisplayName: () => getSessionDisplayName,
43
+ isSessionActive: () => isSessionActive,
44
+ useClaudeBridge: () => useClaudeBridge,
45
+ useSessionStorage: () => useSessionStorage,
46
+ useSessions: () => useSessions,
47
+ useVoiceInput: () => useVoiceInput
40
48
  });
41
49
  module.exports = __toCommonJS(index_exports);
42
50
 
43
51
  // src/ClaudeBridgeProvider.tsx
44
- var import_react6 = require("react");
52
+ var import_react9 = require("react");
45
53
 
46
54
  // src/websocket-client.ts
47
55
  var BridgeWebSocketClient = class {
@@ -704,275 +712,564 @@ function SelectionOverlay({
704
712
  ] });
705
713
  }
706
714
 
707
- // src/PromptDialog.tsx
715
+ // src/components/SessionPanel.tsx
716
+ var import_react7 = require("react");
717
+
718
+ // src/ClaudeTerminal.tsx
708
719
  var import_react2 = require("react");
720
+ var import_xterm = require("@xterm/xterm");
721
+ var import_addon_fit = require("@xterm/addon-fit");
722
+ var import_addon_webgl = require("@xterm/addon-webgl");
723
+ var import_xterm2 = require("@xterm/xterm/css/xterm.css");
709
724
  var import_jsx_runtime2 = require("react/jsx-runtime");
710
- function PromptDialog({
711
- open,
712
- element,
713
- onSubmit,
714
- onClose,
715
- onSwitchToQuestionMode,
716
- isSubmitting = false
725
+ var XTERM_CRITICAL_CSS = `
726
+ .xterm {
727
+ cursor: text;
728
+ position: relative;
729
+ user-select: none;
730
+ -ms-user-select: none;
731
+ -webkit-user-select: none;
732
+ }
733
+ .xterm.focus,
734
+ .xterm:focus {
735
+ outline: none;
736
+ }
737
+ .xterm .xterm-helpers {
738
+ position: absolute;
739
+ top: 0;
740
+ z-index: 5;
741
+ }
742
+ .xterm .xterm-helper-textarea {
743
+ padding: 0;
744
+ border: 0;
745
+ margin: 0;
746
+ position: absolute;
747
+ opacity: 0;
748
+ left: -9999em;
749
+ top: 0;
750
+ width: 0;
751
+ height: 0;
752
+ z-index: -5;
753
+ white-space: nowrap;
754
+ overflow: hidden;
755
+ resize: none;
756
+ }
757
+ .xterm .xterm-helper-textarea:focus {
758
+ outline: none;
759
+ }
760
+ .xterm .composition-view {
761
+ background: #000;
762
+ color: #FFF;
763
+ display: none;
764
+ position: absolute;
765
+ white-space: nowrap;
766
+ z-index: 1;
767
+ }
768
+ .xterm .composition-view.active {
769
+ display: block;
770
+ }
771
+ .xterm .xterm-viewport {
772
+ background-color: #000;
773
+ overflow-y: scroll;
774
+ cursor: default;
775
+ position: absolute;
776
+ right: 0;
777
+ left: 0;
778
+ top: 0;
779
+ bottom: 0;
780
+ }
781
+ .xterm .xterm-screen {
782
+ position: relative;
783
+ }
784
+ .xterm .xterm-screen canvas {
785
+ position: absolute;
786
+ left: 0;
787
+ top: 0;
788
+ }
789
+ .xterm-char-measure-element {
790
+ display: inline-block;
791
+ visibility: hidden;
792
+ position: absolute;
793
+ top: 0;
794
+ left: -9999em;
795
+ line-height: normal;
796
+ }
797
+ .xterm.enable-mouse-events {
798
+ cursor: default;
799
+ }
800
+ .xterm .xterm-cursor-pointer {
801
+ cursor: pointer;
802
+ }
803
+ .xterm.xterm-cursor-style-block .xterm-cursor:not(.xterm-cursor-blink),
804
+ .xterm.xterm-cursor-style-bar .xterm-cursor:not(.xterm-cursor-blink),
805
+ .xterm.xterm-cursor-style-underline .xterm-cursor:not(.xterm-cursor-blink) {
806
+ visibility: visible;
807
+ }
808
+ `;
809
+ function ClaudeTerminal({
810
+ serverUrl,
811
+ token,
812
+ sessionId,
813
+ onSessionSelect,
814
+ onConnectionChange,
815
+ onSessionsUpdate,
816
+ terminalOptions = {},
817
+ style,
818
+ className
717
819
  }) {
718
- const [prompt, setPrompt] = (0, import_react2.useState)("");
719
- const inputRef = (0, import_react2.useRef)(null);
820
+ const containerRef = (0, import_react2.useRef)(null);
821
+ const terminalRef = (0, import_react2.useRef)(null);
822
+ const fitAddonRef = (0, import_react2.useRef)(null);
823
+ const wsRef = (0, import_react2.useRef)(null);
824
+ const handleMessageRef = (0, import_react2.useRef)(null);
825
+ const [connected, setConnected] = (0, import_react2.useState)(false);
826
+ const [sessions, setSessions] = (0, import_react2.useState)([]);
827
+ const [attachedSessionId, setAttachedSessionId] = (0, import_react2.useState)(null);
828
+ const [error, setError] = (0, import_react2.useState)(null);
720
829
  (0, import_react2.useEffect)(() => {
721
- if (open && inputRef.current) {
722
- inputRef.current.focus();
830
+ if (!containerRef.current) return;
831
+ const terminal = new import_xterm.Terminal({
832
+ cursorBlink: true,
833
+ cursorStyle: "block",
834
+ fontSize: terminalOptions.fontSize ?? 14,
835
+ fontFamily: terminalOptions.fontFamily ?? 'Menlo, Monaco, "Courier New", monospace',
836
+ theme: {
837
+ background: terminalOptions.theme?.background ?? "#1a1a1a",
838
+ foreground: terminalOptions.theme?.foreground ?? "#f0f0f0",
839
+ cursor: terminalOptions.theme?.cursor ?? "#f0f0f0",
840
+ cursorAccent: "#1a1a1a",
841
+ black: "#1a1a1a",
842
+ red: "#ff5555",
843
+ green: "#50fa7b",
844
+ yellow: "#f1fa8c",
845
+ blue: "#6272a4",
846
+ magenta: "#ff79c6",
847
+ cyan: "#8be9fd",
848
+ white: "#f8f8f2",
849
+ brightBlack: "#6272a4",
850
+ brightRed: "#ff6e6e",
851
+ brightGreen: "#69ff94",
852
+ brightYellow: "#ffffa5",
853
+ brightBlue: "#d6acff",
854
+ brightMagenta: "#ff92df",
855
+ brightCyan: "#a4ffff",
856
+ brightWhite: "#ffffff"
857
+ },
858
+ allowProposedApi: true,
859
+ scrollback: 1e4,
860
+ disableStdin: false,
861
+ allowTransparency: true
862
+ });
863
+ const fitAddon = new import_addon_fit.FitAddon();
864
+ terminal.loadAddon(fitAddon);
865
+ terminal.open(containerRef.current);
866
+ try {
867
+ const webglAddon = new import_addon_webgl.WebglAddon();
868
+ webglAddon.onContextLoss(() => {
869
+ webglAddon.dispose();
870
+ });
871
+ terminal.loadAddon(webglAddon);
872
+ } catch (e) {
873
+ console.warn("[ClaudeTerminal] WebGL addon could not be loaded:", e);
723
874
  }
724
- }, [open]);
875
+ terminalRef.current = terminal;
876
+ fitAddonRef.current = fitAddon;
877
+ requestAnimationFrame(() => {
878
+ fitAddon.fit();
879
+ });
880
+ const resizeObserver = new ResizeObserver(() => {
881
+ requestAnimationFrame(() => {
882
+ fitAddon.fit();
883
+ if (wsRef.current?.readyState === WebSocket.OPEN && attachedSessionId) {
884
+ wsRef.current.send(JSON.stringify({
885
+ type: "terminal_resize",
886
+ sessionId: attachedSessionId,
887
+ cols: terminal.cols,
888
+ rows: terminal.rows
889
+ }));
890
+ }
891
+ });
892
+ });
893
+ resizeObserver.observe(containerRef.current);
894
+ return () => {
895
+ resizeObserver.disconnect();
896
+ terminal.dispose();
897
+ terminalRef.current = null;
898
+ fitAddonRef.current = null;
899
+ };
900
+ }, [terminalOptions.fontSize, terminalOptions.fontFamily, terminalOptions.theme]);
725
901
  (0, import_react2.useEffect)(() => {
726
- if (!open) {
727
- setPrompt("");
728
- }
729
- }, [open]);
730
- const handleSubmit = () => {
731
- if (!prompt.trim() || isSubmitting) return;
732
- onSubmit(prompt.trim());
733
- };
734
- const handleKeyDown = (e) => {
735
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
736
- e.preventDefault();
737
- handleSubmit();
738
- }
739
- if (e.key === "Escape") {
740
- e.preventDefault();
741
- onClose();
742
- }
743
- };
744
- if (!open || !element) return null;
745
- const context = captureContext(element);
746
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
747
- "div",
748
- {
749
- id: "claude-bridge-prompt-dialog",
750
- style: {
751
- position: "fixed",
752
- bottom: 24,
753
- left: "50%",
754
- transform: "translateX(-50%)",
755
- zIndex: 1e6,
756
- width: "100%",
757
- maxWidth: 600,
758
- background: "#1e1e2e",
759
- borderRadius: 12,
760
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
761
- fontFamily: "system-ui, -apple-system, sans-serif",
762
- overflow: "hidden"
763
- },
764
- children: [
765
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
766
- "div",
767
- {
768
- style: {
769
- padding: "12px 16px",
770
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
771
- display: "flex",
772
- alignItems: "center",
773
- gap: 8
774
- },
775
- children: [
776
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
777
- "div",
778
- {
779
- style: {
780
- width: 8,
781
- height: 8,
782
- borderRadius: "50%",
783
- background: "#6366f1"
784
- }
785
- }
786
- ),
787
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
788
- "span",
789
- {
790
- style: {
791
- color: "rgba(255, 255, 255, 0.9)",
792
- fontSize: 13,
793
- fontWeight: 500
794
- },
795
- children: context.componentName || context.selectedElement.tagName
796
- }
797
- ),
798
- context.sourceFile && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
799
- "span",
800
- {
801
- style: {
802
- color: "rgba(255, 255, 255, 0.5)",
803
- fontSize: 12,
804
- fontFamily: "ui-monospace, monospace"
805
- },
806
- children: [
807
- context.sourceFile,
808
- context.sourceLine ? `:${context.sourceLine}` : ""
809
- ]
810
- }
811
- ),
812
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { flex: 1 } }),
813
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
814
- "button",
815
- {
816
- onClick: onClose,
817
- style: {
818
- background: "transparent",
819
- border: "none",
820
- color: "rgba(255, 255, 255, 0.5)",
821
- cursor: "pointer",
822
- padding: 4,
823
- borderRadius: 4,
824
- fontSize: 18,
825
- lineHeight: 1
826
- },
827
- children: "\xD7"
828
- }
829
- )
830
- ]
831
- }
832
- ),
833
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: 16 }, children: [
834
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
835
- "textarea",
836
- {
837
- ref: inputRef,
838
- value: prompt,
839
- onChange: (e) => setPrompt(e.target.value),
840
- onKeyDown: handleKeyDown,
841
- placeholder: "What would you like Claude to change?",
842
- disabled: isSubmitting,
843
- style: {
844
- width: "100%",
845
- minHeight: 80,
846
- maxHeight: 200,
847
- padding: 12,
848
- background: "rgba(255, 255, 255, 0.05)",
849
- border: "1px solid rgba(255, 255, 255, 0.1)",
850
- borderRadius: 8,
851
- color: "white",
852
- fontSize: 14,
853
- fontFamily: "inherit",
854
- resize: "vertical",
855
- outline: "none"
856
- }
857
- }
858
- ),
859
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
860
- "div",
861
- {
862
- style: {
863
- display: "flex",
864
- flexWrap: "wrap",
865
- gap: 8,
866
- marginTop: 12
867
- },
868
- children: ["Make this bigger", "Change the color", "Add padding", "Hide this element"].map(
869
- (suggestion) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
870
- "button",
871
- {
872
- onClick: () => setPrompt(suggestion),
873
- disabled: isSubmitting,
874
- style: {
875
- background: "rgba(255, 255, 255, 0.05)",
876
- border: "1px solid rgba(255, 255, 255, 0.1)",
877
- borderRadius: 4,
878
- padding: "4px 8px",
879
- color: "rgba(255, 255, 255, 0.7)",
880
- fontSize: 12,
881
- cursor: "pointer"
882
- },
883
- children: suggestion
884
- },
885
- suggestion
886
- )
887
- )
902
+ const terminal = terminalRef.current;
903
+ if (!terminal) return;
904
+ const disposable = terminal.onData((data) => {
905
+ if (wsRef.current?.readyState === WebSocket.OPEN && attachedSessionId) {
906
+ wsRef.current.send(JSON.stringify({
907
+ type: "terminal_input",
908
+ sessionId: attachedSessionId,
909
+ data
910
+ }));
911
+ }
912
+ });
913
+ return () => disposable.dispose();
914
+ }, [attachedSessionId]);
915
+ const attachedSessionIdRef = (0, import_react2.useRef)(null);
916
+ attachedSessionIdRef.current = attachedSessionId;
917
+ (0, import_react2.useEffect)(() => {
918
+ handleMessageRef.current = (event) => {
919
+ try {
920
+ const message = JSON.parse(event.data);
921
+ switch (message.type) {
922
+ case "connected":
923
+ wsRef.current?.send(JSON.stringify({ type: "get_sessions" }));
924
+ break;
925
+ case "sessions_list":
926
+ if (message.sessions) {
927
+ setSessions(message.sessions);
928
+ onSessionsUpdate?.(message.sessions);
888
929
  }
889
- )
890
- ] }),
891
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
892
- "div",
893
- {
894
- style: {
895
- padding: "12px 16px",
896
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
897
- display: "flex",
898
- justifyContent: "space-between",
899
- alignItems: "center"
930
+ break;
931
+ case "terminal_attached":
932
+ if (message.sessionId) {
933
+ setAttachedSessionId(message.sessionId);
934
+ setError(null);
935
+ if (message.scrollback && terminalRef.current) {
936
+ terminalRef.current.write(message.scrollback);
937
+ }
938
+ if (fitAddonRef.current && terminalRef.current) {
939
+ fitAddonRef.current.fit();
940
+ wsRef.current?.send(JSON.stringify({
941
+ type: "terminal_resize",
942
+ sessionId: message.sessionId,
943
+ cols: terminalRef.current.cols,
944
+ rows: terminalRef.current.rows
945
+ }));
946
+ }
947
+ }
948
+ break;
949
+ case "terminal_output":
950
+ if (message.data && terminalRef.current && message.sessionId === attachedSessionIdRef.current) {
951
+ terminalRef.current.write(message.data);
952
+ }
953
+ break;
954
+ case "terminal_detached":
955
+ if (message.sessionId === attachedSessionIdRef.current) {
956
+ setAttachedSessionId(null);
957
+ }
958
+ break;
959
+ case "error":
960
+ setError(message.error || "Unknown error");
961
+ console.error("[ClaudeTerminal] Error:", message.error);
962
+ break;
963
+ case "pong":
964
+ break;
965
+ }
966
+ } catch (e) {
967
+ console.error("[ClaudeTerminal] Failed to parse message:", e);
968
+ }
969
+ };
970
+ }, [onSessionsUpdate]);
971
+ (0, import_react2.useEffect)(() => {
972
+ const wsUrl = serverUrl.replace(/^http/, "ws").replace(/\/$/, "");
973
+ const ws = new WebSocket(`${wsUrl}/ws/bridge?token=${encodeURIComponent(token)}`);
974
+ ws.onopen = () => {
975
+ setConnected(true);
976
+ setError(null);
977
+ onConnectionChange?.(true);
978
+ };
979
+ ws.onmessage = (event) => {
980
+ handleMessageRef.current?.(event);
981
+ };
982
+ ws.onclose = () => {
983
+ setConnected(false);
984
+ setAttachedSessionId(null);
985
+ onConnectionChange?.(false);
986
+ };
987
+ ws.onerror = () => {
988
+ setError("WebSocket connection error");
989
+ };
990
+ wsRef.current = ws;
991
+ const pingInterval = setInterval(() => {
992
+ if (ws.readyState === WebSocket.OPEN) {
993
+ ws.send(JSON.stringify({ type: "ping" }));
994
+ }
995
+ }, 3e4);
996
+ return () => {
997
+ clearInterval(pingInterval);
998
+ ws.close();
999
+ wsRef.current = null;
1000
+ };
1001
+ }, [serverUrl, token, onConnectionChange]);
1002
+ (0, import_react2.useEffect)(() => {
1003
+ if (sessionId && connected && !attachedSessionId) {
1004
+ wsRef.current?.send(JSON.stringify({
1005
+ type: "attach_terminal",
1006
+ sessionId
1007
+ }));
1008
+ }
1009
+ }, [sessionId, connected, attachedSessionId]);
1010
+ (0, import_react2.useEffect)(() => {
1011
+ if (attachedSessionId && terminalRef.current && fitAddonRef.current) {
1012
+ const timer = setTimeout(() => {
1013
+ fitAddonRef.current?.fit();
1014
+ terminalRef.current?.focus();
1015
+ }, 50);
1016
+ return () => clearTimeout(timer);
1017
+ }
1018
+ }, [attachedSessionId]);
1019
+ const handleContainerClick = (0, import_react2.useCallback)(() => {
1020
+ terminalRef.current?.focus();
1021
+ }, []);
1022
+ const handleSelectSession = (0, import_react2.useCallback)((session) => {
1023
+ terminalRef.current?.clear();
1024
+ if (attachedSessionId) {
1025
+ wsRef.current?.send(JSON.stringify({
1026
+ type: "detach_terminal",
1027
+ sessionId: attachedSessionId
1028
+ }));
1029
+ }
1030
+ wsRef.current?.send(JSON.stringify({
1031
+ type: "attach_terminal",
1032
+ sessionId: session.id
1033
+ }));
1034
+ onSessionSelect?.(session);
1035
+ }, [attachedSessionId, onSessionSelect]);
1036
+ if (!sessionId && !attachedSessionId) {
1037
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1038
+ "div",
1039
+ {
1040
+ className,
1041
+ style: {
1042
+ background: "#1a1a1a",
1043
+ color: "#f0f0f0",
1044
+ padding: "20px",
1045
+ fontFamily: 'Menlo, Monaco, "Courier New", monospace',
1046
+ ...style
1047
+ },
1048
+ children: !connected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "Connecting to server..." }) : error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "#ff5555" }, children: [
1049
+ "Error: ",
1050
+ error
1051
+ ] }) : sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "No Claude sessions available" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginBottom: "10px", fontSize: "14px" }, children: "Select a Claude session:" }),
1053
+ sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1054
+ "div",
1055
+ {
1056
+ onClick: () => handleSelectSession(session),
1057
+ style: {
1058
+ padding: "10px",
1059
+ margin: "5px 0",
1060
+ background: "#2a2a2a",
1061
+ borderRadius: "4px",
1062
+ cursor: "pointer",
1063
+ border: "1px solid #3a3a3a"
1064
+ },
1065
+ onMouseOver: (e) => {
1066
+ e.currentTarget.style.background = "#3a3a3a";
1067
+ },
1068
+ onMouseOut: (e) => {
1069
+ e.currentTarget.style.background = "#2a2a2a";
1070
+ },
1071
+ children: [
1072
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: "bold" }, children: session.displayName || session.name }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "12px", color: "#888", marginTop: "4px" }, children: session.workingDir })
1074
+ ]
900
1075
  },
901
- children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
903
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1076
+ session.id
1077
+ ))
1078
+ ] })
1079
+ }
1080
+ );
1081
+ }
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: XTERM_CRITICAL_CSS }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1085
+ "div",
1086
+ {
1087
+ ref: containerRef,
1088
+ className,
1089
+ onClick: handleContainerClick,
1090
+ style: {
1091
+ width: "100%",
1092
+ height: "100%",
1093
+ ...style
1094
+ }
1095
+ }
1096
+ )
1097
+ ] });
1098
+ }
1099
+
1100
+ // src/components/SessionTabs.tsx
1101
+ var import_react4 = require("react");
1102
+
1103
+ // src/hooks/useSessions.ts
1104
+ var import_react3 = require("react");
1105
+ function useSessions({
1106
+ sessions,
1107
+ projectRoot
1108
+ }) {
1109
+ const isProjectSession = (0, import_react3.useMemo)(() => {
1110
+ return (session) => {
1111
+ if (!projectRoot) return true;
1112
+ return session.workingDir === projectRoot || session.workingDir.startsWith(projectRoot + "/");
1113
+ };
1114
+ }, [projectRoot]);
1115
+ const projectSessions = (0, import_react3.useMemo)(() => {
1116
+ if (!projectRoot) return sessions;
1117
+ return sessions.filter(isProjectSession);
1118
+ }, [sessions, projectRoot, isProjectSession]);
1119
+ const sortedProjectSessions = (0, import_react3.useMemo)(() => {
1120
+ return [...projectSessions].sort((a, b) => {
1121
+ return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime();
1122
+ });
1123
+ }, [projectSessions]);
1124
+ return {
1125
+ projectSessions: sortedProjectSessions,
1126
+ allSessions: sessions,
1127
+ isProjectSession
1128
+ };
1129
+ }
1130
+ function getSessionDisplayName(session) {
1131
+ return session.displayName || session.name || session.tmuxSession;
1132
+ }
1133
+ function isSessionActive(session) {
1134
+ const lastActivity = new Date(session.lastActivity).getTime();
1135
+ const now = Date.now();
1136
+ const sixtySecondsAgo = now - 60 * 1e3;
1137
+ return lastActivity > sixtySecondsAgo;
1138
+ }
1139
+
1140
+ // src/components/SessionTabs.tsx
1141
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1142
+ function SessionTabs({
1143
+ sessions,
1144
+ activeSessionId,
1145
+ onSessionSelect,
1146
+ onSessionClose,
1147
+ taskSessionId
1148
+ }) {
1149
+ const handleTabClick = (0, import_react4.useCallback)(
1150
+ (e, sessionId) => {
1151
+ e.stopPropagation();
1152
+ onSessionSelect(sessionId);
1153
+ },
1154
+ [onSessionSelect]
1155
+ );
1156
+ const handleCloseClick = (0, import_react4.useCallback)(
1157
+ (e, sessionId) => {
1158
+ e.stopPropagation();
1159
+ onSessionClose?.(sessionId);
1160
+ },
1161
+ [onSessionClose]
1162
+ );
1163
+ if (sessions.length === 0) {
1164
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1165
+ "div",
1166
+ {
1167
+ style: {
1168
+ padding: "8px 12px",
1169
+ color: "rgba(255, 255, 255, 0.5)",
1170
+ fontSize: 12
1171
+ },
1172
+ children: "No active sessions"
1173
+ }
1174
+ );
1175
+ }
1176
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1177
+ "div",
1178
+ {
1179
+ style: {
1180
+ display: "flex",
1181
+ alignItems: "center",
1182
+ gap: 2,
1183
+ padding: "4px 8px",
1184
+ overflowX: "auto",
1185
+ flex: 1,
1186
+ minWidth: 0
1187
+ },
1188
+ children: [
1189
+ sessions.map((session) => {
1190
+ const isActive = session.id === activeSessionId;
1191
+ const isRunning = isSessionActive(session);
1192
+ const isTaskSession = session.id === taskSessionId;
1193
+ const displayName = getSessionDisplayName(session);
1194
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1195
+ "button",
1196
+ {
1197
+ onClick: (e) => handleTabClick(e, session.id),
1198
+ title: `${displayName}
1199
+ ${session.workingDir}`,
1200
+ style: {
1201
+ display: "flex",
1202
+ alignItems: "center",
1203
+ gap: 6,
1204
+ padding: "6px 10px",
1205
+ background: isActive ? "rgba(99, 102, 241, 0.2)" : "rgba(255, 255, 255, 0.05)",
1206
+ border: isActive ? "1px solid rgba(99, 102, 241, 0.4)" : "1px solid transparent",
1207
+ borderRadius: 6,
1208
+ color: isActive ? "rgba(255, 255, 255, 0.95)" : "rgba(255, 255, 255, 0.7)",
1209
+ fontSize: 12,
1210
+ fontWeight: isActive ? 500 : 400,
1211
+ cursor: "pointer",
1212
+ whiteSpace: "nowrap",
1213
+ maxWidth: 180,
1214
+ overflow: "hidden",
1215
+ textOverflow: "ellipsis",
1216
+ transition: "all 0.15s ease"
1217
+ },
1218
+ children: [
1219
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
904
1220
  "span",
905
1221
  {
906
1222
  style: {
907
- color: "rgba(255, 255, 255, 0.4)",
908
- fontSize: 12
909
- },
910
- children: [
911
- navigator.platform.includes("Mac") ? "\u2318" : "Ctrl",
912
- "+Enter to submit"
913
- ]
1223
+ width: 6,
1224
+ height: 6,
1225
+ borderRadius: "50%",
1226
+ background: isTaskSession ? "#22c55e" : isRunning ? "#f59e0b" : "rgba(255, 255, 255, 0.3)",
1227
+ flexShrink: 0,
1228
+ animation: isTaskSession ? "claude-bridge-pulse 1.5s infinite" : "none"
1229
+ }
914
1230
  }
915
1231
  ),
916
- onSwitchToQuestionMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
917
- "button",
1232
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1233
+ "span",
918
1234
  {
919
- onClick: onSwitchToQuestionMode,
920
- disabled: isSubmitting,
921
1235
  style: {
922
- background: "transparent",
923
- border: "none",
924
- color: "#22c55e",
925
- fontSize: 12,
926
- cursor: "pointer",
927
- padding: 0,
928
- textDecoration: "underline"
1236
+ overflow: "hidden",
1237
+ textOverflow: "ellipsis"
929
1238
  },
930
- children: "Just ask a question instead"
1239
+ children: displayName
931
1240
  }
932
- )
933
- ] }),
934
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
935
- "button",
936
- {
937
- onClick: handleSubmit,
938
- disabled: !prompt.trim() || isSubmitting,
939
- style: {
940
- background: prompt.trim() && !isSubmitting ? "#6366f1" : "rgba(99, 102, 241, 0.3)",
941
- border: "none",
942
- borderRadius: 6,
943
- padding: "8px 16px",
944
- color: "white",
945
- fontSize: 14,
946
- fontWeight: 500,
947
- cursor: prompt.trim() && !isSubmitting ? "pointer" : "not-allowed",
948
- display: "flex",
949
- alignItems: "center",
950
- gap: 6
951
- },
952
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
953
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
954
- "span",
955
- {
956
- style: {
957
- width: 14,
958
- height: 14,
959
- border: "2px solid rgba(255, 255, 255, 0.3)",
960
- borderTopColor: "white",
961
- borderRadius: "50%",
962
- animation: "claude-bridge-spin 0.8s linear infinite"
963
- }
964
- }
965
- ),
966
- "Sending..."
967
- ] }) : "Send to Claude"
968
- }
969
- )
970
- ]
971
- }
972
- ),
973
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
974
- @keyframes claude-bridge-spin {
975
- to { transform: rotate(360deg); }
1241
+ ),
1242
+ onSessionClose && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1243
+ "span",
1244
+ {
1245
+ onClick: (e) => handleCloseClick(e, session.id),
1246
+ style: {
1247
+ marginLeft: 2,
1248
+ padding: "0 2px",
1249
+ color: "rgba(255, 255, 255, 0.4)",
1250
+ cursor: "pointer",
1251
+ fontSize: 14,
1252
+ lineHeight: 1,
1253
+ flexShrink: 0
1254
+ },
1255
+ onMouseOver: (e) => {
1256
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.8)";
1257
+ },
1258
+ onMouseOut: (e) => {
1259
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.4)";
1260
+ },
1261
+ children: "\xD7"
1262
+ }
1263
+ )
1264
+ ]
1265
+ },
1266
+ session.id
1267
+ );
1268
+ }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
1270
+ @keyframes claude-bridge-pulse {
1271
+ 0%, 100% { opacity: 1; }
1272
+ 50% { opacity: 0.5; }
976
1273
  }
977
1274
  ` })
978
1275
  ]
@@ -980,343 +1277,984 @@ function PromptDialog({
980
1277
  );
981
1278
  }
982
1279
 
983
- // src/QuestionDialog.tsx
984
- var import_react3 = require("react");
985
- var import_jsx_runtime3 = require("react/jsx-runtime");
986
- function QuestionDialog({
987
- open,
1280
+ // src/components/VoiceInput.tsx
1281
+ var import_react6 = __toESM(require("react"), 1);
1282
+
1283
+ // src/hooks/useVoiceInput.ts
1284
+ var import_react5 = require("react");
1285
+ function useVoiceInput(options = {}) {
1286
+ const {
1287
+ onTranscript,
1288
+ onError,
1289
+ onStart,
1290
+ onEnd,
1291
+ language = "en-US"
1292
+ } = options;
1293
+ const [isListening, setIsListening] = (0, import_react5.useState)(false);
1294
+ const [transcript, setTranscript] = (0, import_react5.useState)("");
1295
+ const [interimTranscript, setInterimTranscript] = (0, import_react5.useState)("");
1296
+ const [error, setError] = (0, import_react5.useState)(null);
1297
+ const recognitionRef = (0, import_react5.useRef)(null);
1298
+ const isSupported = typeof window !== "undefined" && (!!window.SpeechRecognition || !!window.webkitSpeechRecognition);
1299
+ (0, import_react5.useEffect)(() => {
1300
+ return () => {
1301
+ if (recognitionRef.current) {
1302
+ recognitionRef.current.abort();
1303
+ recognitionRef.current = null;
1304
+ }
1305
+ };
1306
+ }, []);
1307
+ const startListening = (0, import_react5.useCallback)(() => {
1308
+ if (!isSupported) {
1309
+ const errMsg = "Speech recognition is not supported in this browser";
1310
+ setError(errMsg);
1311
+ onError?.(errMsg);
1312
+ return;
1313
+ }
1314
+ if (recognitionRef.current) {
1315
+ recognitionRef.current.abort();
1316
+ }
1317
+ setError(null);
1318
+ setTranscript("");
1319
+ setInterimTranscript("");
1320
+ const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;
1321
+ if (!SpeechRecognitionClass) return;
1322
+ const recognition = new SpeechRecognitionClass();
1323
+ recognitionRef.current = recognition;
1324
+ recognition.continuous = false;
1325
+ recognition.interimResults = true;
1326
+ recognition.lang = language;
1327
+ recognition.onstart = () => {
1328
+ setIsListening(true);
1329
+ onStart?.();
1330
+ };
1331
+ recognition.onresult = (event) => {
1332
+ let finalTranscript = "";
1333
+ let interimText = "";
1334
+ for (let i = event.resultIndex; i < event.results.length; i++) {
1335
+ const result = event.results[i];
1336
+ if (result.isFinal) {
1337
+ finalTranscript += result[0].transcript;
1338
+ } else {
1339
+ interimText += result[0].transcript;
1340
+ }
1341
+ }
1342
+ if (finalTranscript) {
1343
+ setTranscript((prev) => prev + finalTranscript);
1344
+ setInterimTranscript("");
1345
+ onTranscript?.(finalTranscript, true);
1346
+ } else {
1347
+ setInterimTranscript(interimText);
1348
+ onTranscript?.(interimText, false);
1349
+ }
1350
+ };
1351
+ recognition.onerror = (event) => {
1352
+ if (event.error === "no-speech" || event.error === "aborted") {
1353
+ return;
1354
+ }
1355
+ const errMsg = `Speech recognition error: ${event.error}`;
1356
+ setError(errMsg);
1357
+ setIsListening(false);
1358
+ onError?.(errMsg);
1359
+ };
1360
+ recognition.onend = () => {
1361
+ setIsListening(false);
1362
+ setInterimTranscript("");
1363
+ onEnd?.();
1364
+ recognitionRef.current = null;
1365
+ };
1366
+ try {
1367
+ recognition.start();
1368
+ } catch (err) {
1369
+ const errMsg = "Failed to start speech recognition";
1370
+ setError(errMsg);
1371
+ onError?.(errMsg);
1372
+ }
1373
+ }, [isSupported, language, onTranscript, onError, onStart, onEnd]);
1374
+ const stopListening = (0, import_react5.useCallback)(() => {
1375
+ if (recognitionRef.current) {
1376
+ recognitionRef.current.stop();
1377
+ }
1378
+ }, []);
1379
+ return {
1380
+ isListening,
1381
+ isSupported,
1382
+ transcript,
1383
+ interimTranscript,
1384
+ startListening,
1385
+ stopListening,
1386
+ error
1387
+ };
1388
+ }
1389
+
1390
+ // src/components/VoiceInput.tsx
1391
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1392
+ function VoiceInput({
1393
+ onTranscript,
1394
+ onListeningChange,
1395
+ disabled = false,
1396
+ size = "medium",
1397
+ autoStart = false
1398
+ }) {
1399
+ const autoStartTriggeredRef = import_react6.default.useRef(false);
1400
+ const {
1401
+ isListening,
1402
+ isSupported,
1403
+ transcript,
1404
+ interimTranscript,
1405
+ startListening,
1406
+ stopListening
1407
+ } = useVoiceInput({
1408
+ onTranscript: (text, isFinal) => {
1409
+ if (isFinal) {
1410
+ onTranscript(text);
1411
+ }
1412
+ }
1413
+ });
1414
+ (0, import_react6.useEffect)(() => {
1415
+ if (autoStart && isSupported && !disabled && !autoStartTriggeredRef.current) {
1416
+ autoStartTriggeredRef.current = true;
1417
+ const timer = setTimeout(() => {
1418
+ startListening();
1419
+ }, 100);
1420
+ return () => clearTimeout(timer);
1421
+ }
1422
+ }, [autoStart, isSupported, disabled, startListening]);
1423
+ (0, import_react6.useEffect)(() => {
1424
+ onListeningChange?.(isListening);
1425
+ }, [isListening, onListeningChange]);
1426
+ (0, import_react6.useEffect)(() => {
1427
+ if (!isListening && transcript) {
1428
+ onTranscript(transcript);
1429
+ }
1430
+ }, [isListening, transcript, onTranscript]);
1431
+ const handleClick = (0, import_react6.useCallback)(() => {
1432
+ if (isListening) {
1433
+ stopListening();
1434
+ } else {
1435
+ startListening();
1436
+ }
1437
+ }, [isListening, startListening, stopListening]);
1438
+ if (!isSupported) {
1439
+ return null;
1440
+ }
1441
+ const buttonSize = size === "small" ? 32 : 40;
1442
+ const iconSize = size === "small" ? 16 : 20;
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
1444
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1445
+ "button",
1446
+ {
1447
+ type: "button",
1448
+ onClick: handleClick,
1449
+ disabled,
1450
+ title: isListening ? "Stop listening" : "Start voice input",
1451
+ style: {
1452
+ width: buttonSize,
1453
+ height: buttonSize,
1454
+ borderRadius: "50%",
1455
+ border: "none",
1456
+ background: isListening ? "#ef4444" : disabled ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.1)",
1457
+ cursor: disabled ? "not-allowed" : "pointer",
1458
+ display: "flex",
1459
+ alignItems: "center",
1460
+ justifyContent: "center",
1461
+ transition: "all 0.2s ease",
1462
+ position: "relative",
1463
+ overflow: "visible"
1464
+ },
1465
+ children: [
1466
+ isListening && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1467
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1468
+ "span",
1469
+ {
1470
+ style: {
1471
+ position: "absolute",
1472
+ top: -4,
1473
+ left: -4,
1474
+ right: -4,
1475
+ bottom: -4,
1476
+ borderRadius: "50%",
1477
+ border: "2px solid #ef4444",
1478
+ animation: "claude-voice-pulse 1.5s ease-out infinite"
1479
+ }
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1483
+ "span",
1484
+ {
1485
+ style: {
1486
+ position: "absolute",
1487
+ top: -8,
1488
+ left: -8,
1489
+ right: -8,
1490
+ bottom: -8,
1491
+ borderRadius: "50%",
1492
+ border: "2px solid #ef4444",
1493
+ animation: "claude-voice-pulse 1.5s ease-out infinite 0.5s"
1494
+ }
1495
+ }
1496
+ )
1497
+ ] }),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1499
+ "svg",
1500
+ {
1501
+ width: iconSize,
1502
+ height: iconSize,
1503
+ viewBox: "0 0 24 24",
1504
+ fill: "none",
1505
+ stroke: isListening ? "white" : disabled ? "rgba(255, 255, 255, 0.3)" : "rgba(255, 255, 255, 0.7)",
1506
+ strokeWidth: "2",
1507
+ strokeLinecap: "round",
1508
+ strokeLinejoin: "round",
1509
+ children: [
1510
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1512
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1513
+ ]
1514
+ }
1515
+ )
1516
+ ]
1517
+ }
1518
+ ),
1519
+ isListening && (interimTranscript || transcript) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1520
+ "div",
1521
+ {
1522
+ style: {
1523
+ position: "absolute",
1524
+ bottom: "100%",
1525
+ left: "50%",
1526
+ transform: "translateX(-50%)",
1527
+ marginBottom: 8,
1528
+ padding: "8px 12px",
1529
+ background: "rgba(0, 0, 0, 0.9)",
1530
+ borderRadius: 8,
1531
+ maxWidth: 300,
1532
+ whiteSpace: "pre-wrap",
1533
+ color: "white",
1534
+ fontSize: 13,
1535
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)"
1536
+ },
1537
+ children: [
1538
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.5)", fontSize: 11, marginBottom: 4 }, children: "Listening..." }),
1539
+ transcript,
1540
+ interimTranscript && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "rgba(255, 255, 255, 0.5)" }, children: interimTranscript })
1541
+ ]
1542
+ }
1543
+ ),
1544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
1545
+ @keyframes claude-voice-pulse {
1546
+ 0% {
1547
+ transform: scale(1);
1548
+ opacity: 0.8;
1549
+ }
1550
+ 100% {
1551
+ transform: scale(1.5);
1552
+ opacity: 0;
1553
+ }
1554
+ }
1555
+ ` })
1556
+ ] });
1557
+ }
1558
+
1559
+ // src/components/SessionPanel.tsx
1560
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1561
+ var MIN_WIDTH = 400;
1562
+ var MIN_HEIGHT = 300;
1563
+ var DEFAULT_WIDTH = 700;
1564
+ var DEFAULT_HEIGHT = 500;
1565
+ function SessionPanel({
1566
+ sessions,
1567
+ activeSessionId,
1568
+ onSessionSelect,
1569
+ onSessionClose,
1570
+ serverUrl,
1571
+ token,
1572
+ expanded,
1573
+ onExpandedChange,
988
1574
  selectedElement,
989
- onSubmit,
1575
+ mode,
1576
+ onModeChange,
1577
+ onPromptSubmit,
1578
+ onQuestionSubmit,
990
1579
  onClose,
991
- onSwitchToTaskMode,
992
- isSubmitting = false
1580
+ isSubmitting,
1581
+ voiceInputActive = false,
1582
+ onVoiceInputStart,
1583
+ activeTask
993
1584
  }) {
994
- const [prompt, setPrompt] = (0, import_react3.useState)("");
995
- const [includeElement, setIncludeElement] = (0, import_react3.useState)(true);
996
- const inputRef = (0, import_react3.useRef)(null);
997
- (0, import_react3.useEffect)(() => {
998
- if (open && inputRef.current) {
1585
+ const [size, setSize] = (0, import_react7.useState)({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT });
1586
+ const [isResizing, setIsResizing] = (0, import_react7.useState)(false);
1587
+ const [isSaving, setIsSaving] = (0, import_react7.useState)(false);
1588
+ const [prompt, setPrompt] = (0, import_react7.useState)("");
1589
+ const [includeElement, setIncludeElement] = (0, import_react7.useState)(true);
1590
+ const [isVoiceListening, setIsVoiceListening] = (0, import_react7.useState)(false);
1591
+ const resizeRef = (0, import_react7.useRef)(null);
1592
+ const inputRef = (0, import_react7.useRef)(null);
1593
+ (0, import_react7.useEffect)(() => {
1594
+ setIncludeElement(!!selectedElement);
1595
+ }, [selectedElement]);
1596
+ (0, import_react7.useEffect)(() => {
1597
+ if (mode === "terminal") {
1598
+ setPrompt("");
1599
+ }
1600
+ }, [mode]);
1601
+ (0, import_react7.useEffect)(() => {
1602
+ if ((mode === "prompt" || mode === "question") && inputRef.current) {
999
1603
  inputRef.current.focus();
1000
1604
  }
1001
- }, [open]);
1002
- (0, import_react3.useEffect)(() => {
1003
- if (!open) {
1004
- setPrompt("");
1605
+ }, [mode]);
1606
+ const handleResizeStart = (0, import_react7.useCallback)((e, direction) => {
1607
+ e.preventDefault();
1608
+ e.stopPropagation();
1609
+ setIsResizing(true);
1610
+ resizeRef.current = {
1611
+ startX: e.clientX,
1612
+ startY: e.clientY,
1613
+ startWidth: size.width,
1614
+ startHeight: size.height
1615
+ };
1616
+ const handleMouseMove = (moveEvent) => {
1617
+ if (!resizeRef.current) return;
1618
+ let newWidth = resizeRef.current.startWidth;
1619
+ let newHeight = resizeRef.current.startHeight;
1620
+ if (direction.includes("w")) {
1621
+ newWidth = resizeRef.current.startWidth - (moveEvent.clientX - resizeRef.current.startX);
1622
+ }
1623
+ if (direction.includes("n")) {
1624
+ newHeight = resizeRef.current.startHeight - (moveEvent.clientY - resizeRef.current.startY);
1625
+ }
1626
+ setSize({
1627
+ width: Math.max(MIN_WIDTH, newWidth),
1628
+ height: Math.max(MIN_HEIGHT, newHeight)
1629
+ });
1630
+ };
1631
+ const handleMouseUp = () => {
1632
+ setIsResizing(false);
1633
+ resizeRef.current = null;
1634
+ document.removeEventListener("mousemove", handleMouseMove);
1635
+ document.removeEventListener("mouseup", handleMouseUp);
1636
+ };
1637
+ document.addEventListener("mousemove", handleMouseMove);
1638
+ document.addEventListener("mouseup", handleMouseUp);
1639
+ }, [size]);
1640
+ const killSession = (0, import_react7.useCallback)((sessionId, taskPrompt) => {
1641
+ return new Promise((resolve) => {
1642
+ const wsUrl = serverUrl.replace(/^http/, "ws").replace(/\/$/, "");
1643
+ const ws = new WebSocket(`${wsUrl}/ws/bridge?token=${encodeURIComponent(token)}`);
1644
+ let resolved = false;
1645
+ const cleanup = () => {
1646
+ if (!resolved) {
1647
+ resolved = true;
1648
+ ws.close();
1649
+ resolve();
1650
+ }
1651
+ };
1652
+ ws.onopen = () => {
1653
+ ws.send(JSON.stringify({
1654
+ type: "kill_session",
1655
+ sessionId,
1656
+ taskPrompt,
1657
+ autoCommit: true
1658
+ }));
1659
+ };
1660
+ ws.onmessage = (event) => {
1661
+ try {
1662
+ const message = JSON.parse(event.data);
1663
+ if (message.type === "session_killed" || message.type === "error") {
1664
+ cleanup();
1665
+ }
1666
+ } catch {
1667
+ }
1668
+ };
1669
+ ws.onerror = () => cleanup();
1670
+ ws.onclose = () => {
1671
+ if (!resolved) {
1672
+ resolved = true;
1673
+ resolve();
1674
+ }
1675
+ };
1676
+ setTimeout(cleanup, 35e3);
1677
+ });
1678
+ }, [serverUrl, token]);
1679
+ const handleSessionClose = (0, import_react7.useCallback)(async (sessionId) => {
1680
+ setIsSaving(true);
1681
+ try {
1682
+ await killSession(sessionId);
1683
+ onSessionClose?.(sessionId);
1684
+ } finally {
1685
+ setIsSaving(false);
1005
1686
  }
1006
- }, [open]);
1007
- (0, import_react3.useEffect)(() => {
1008
- setIncludeElement(!!selectedElement);
1009
- }, [selectedElement]);
1010
- const handleSubmit = () => {
1687
+ }, [killSession, onSessionClose]);
1688
+ const handleToggleExpand = (0, import_react7.useCallback)(() => {
1689
+ onExpandedChange(!expanded);
1690
+ }, [expanded, onExpandedChange]);
1691
+ const handleDismiss = (0, import_react7.useCallback)(() => {
1692
+ onClose();
1693
+ onExpandedChange(false);
1694
+ }, [onClose, onExpandedChange]);
1695
+ const handleVoiceTranscript = (0, import_react7.useCallback)((text) => {
1696
+ setPrompt((prev) => {
1697
+ const separator = prev.trim() ? " " : "";
1698
+ return prev + separator + text;
1699
+ });
1700
+ inputRef.current?.focus();
1701
+ }, []);
1702
+ const handleVoiceListeningChange = (0, import_react7.useCallback)((listening) => {
1703
+ setIsVoiceListening(listening);
1704
+ if (listening) {
1705
+ onVoiceInputStart?.();
1706
+ }
1707
+ }, [onVoiceInputStart]);
1708
+ const handleSubmit = (0, import_react7.useCallback)(() => {
1011
1709
  if (!prompt.trim() || isSubmitting) return;
1012
- onSubmit(prompt.trim(), includeElement && !!selectedElement);
1013
- };
1014
- const handleKeyDown = (e) => {
1710
+ if (mode === "prompt") {
1711
+ onPromptSubmit(prompt.trim());
1712
+ } else if (mode === "question") {
1713
+ onQuestionSubmit(prompt.trim(), includeElement && !!selectedElement);
1714
+ }
1715
+ setPrompt("");
1716
+ }, [prompt, isSubmitting, mode, onPromptSubmit, onQuestionSubmit, includeElement, selectedElement]);
1717
+ const handleKeyDown = (0, import_react7.useCallback)((e) => {
1015
1718
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
1016
1719
  e.preventDefault();
1017
1720
  handleSubmit();
1018
1721
  }
1019
1722
  if (e.key === "Escape") {
1020
1723
  e.preventDefault();
1021
- onClose();
1724
+ if (mode !== "terminal") {
1725
+ onModeChange("terminal");
1726
+ } else {
1727
+ handleDismiss();
1728
+ }
1022
1729
  }
1023
- };
1024
- if (!open) return null;
1025
- const pageContext = capturePageContext();
1730
+ }, [handleSubmit, mode, onModeChange, handleDismiss]);
1731
+ const handleOpenZed = (0, import_react7.useCallback)(() => {
1732
+ const zedUrl = activeSessionId ? `${serverUrl}?token=${encodeURIComponent(token)}&session=${activeSessionId}` : `${serverUrl}?token=${encodeURIComponent(token)}`;
1733
+ window.open(zedUrl, "_blank");
1734
+ }, [serverUrl, token, activeSessionId]);
1735
+ const hasContent = sessions.length > 0 || mode !== "terminal";
1736
+ if (!hasContent && !activeTask) return null;
1026
1737
  const elementContext = selectedElement ? captureContext(selectedElement) : null;
1027
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1028
- "div",
1029
- {
1030
- id: "claude-bridge-question-dialog",
1031
- style: {
1032
- position: "fixed",
1033
- bottom: 24,
1034
- left: "50%",
1035
- transform: "translateX(-50%)",
1036
- zIndex: 1e6,
1037
- width: "100%",
1038
- maxWidth: 600,
1039
- background: "#1e1e2e",
1040
- borderRadius: 12,
1041
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
1042
- fontFamily: "system-ui, -apple-system, sans-serif",
1043
- overflow: "hidden"
1044
- },
1045
- children: [
1046
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1047
- "div",
1048
- {
1049
- style: {
1050
- padding: "12px 16px",
1051
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
1052
- display: "flex",
1053
- alignItems: "center",
1054
- gap: 8
1055
- },
1056
- children: [
1057
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1058
- "div",
1059
- {
1060
- style: {
1061
- width: 8,
1062
- height: 8,
1063
- borderRadius: "50%",
1064
- background: "#22c55e"
1065
- // Green to differentiate from task dialog
1066
- }
1067
- }
1068
- ),
1069
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1070
- "span",
1071
- {
1072
- style: {
1073
- color: "rgba(255, 255, 255, 0.9)",
1074
- fontSize: 13,
1075
- fontWeight: 500
1076
- },
1077
- children: "Ask a Question"
1078
- }
1079
- ),
1080
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1081
- "span",
1082
- {
1083
- style: {
1084
- color: "rgba(255, 255, 255, 0.5)",
1085
- fontSize: 12
1086
- },
1087
- children: pageContext.pageTitle
1088
- }
1089
- ),
1090
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flex: 1 } }),
1091
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1092
- "button",
1093
- {
1094
- onClick: onClose,
1095
- style: {
1096
- background: "transparent",
1097
- border: "none",
1098
- color: "rgba(255, 255, 255, 0.5)",
1099
- cursor: "pointer",
1100
- padding: 4,
1101
- borderRadius: 4,
1102
- fontSize: 18,
1103
- lineHeight: 1
1104
- },
1105
- children: "\xD7"
1106
- }
1107
- )
1108
- ]
1738
+ const pageContext = capturePageContext();
1739
+ const statusColors = {
1740
+ queued: { bg: "#6366f1", text: "Queued" },
1741
+ starting: { bg: "#f59e0b", text: "Starting..." },
1742
+ running: { bg: "#22c55e", text: "Running" },
1743
+ completed: { bg: "#22c55e", text: "Completed" },
1744
+ failed: { bg: "#ef4444", text: "Failed" },
1745
+ saving: { bg: "#f59e0b", text: "Saving..." }
1746
+ };
1747
+ const status = isSaving ? statusColors.saving : activeTask ? statusColors[activeTask.status] || statusColors.queued : null;
1748
+ if (!expanded) {
1749
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1750
+ "div",
1751
+ {
1752
+ style: {
1753
+ position: "fixed",
1754
+ bottom: 24,
1755
+ right: 24,
1756
+ zIndex: 1000001,
1757
+ background: "#1e1e2e",
1758
+ borderRadius: 12,
1759
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
1760
+ fontFamily: "system-ui, -apple-system, sans-serif",
1761
+ minWidth: 280,
1762
+ maxWidth: 400
1763
+ },
1764
+ children: [
1765
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1766
+ "div",
1767
+ {
1768
+ style: {
1769
+ padding: "12px 16px",
1770
+ display: "flex",
1771
+ alignItems: "center",
1772
+ gap: 10
1773
+ },
1774
+ children: [
1775
+ status && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1776
+ "div",
1777
+ {
1778
+ style: {
1779
+ width: 10,
1780
+ height: 10,
1781
+ borderRadius: "50%",
1782
+ background: status.bg,
1783
+ animation: activeTask?.status === "running" || activeTask?.status === "starting" ? "claude-bridge-pulse 1.5s infinite" : "none"
1784
+ }
1785
+ }
1786
+ ),
1787
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1788
+ "span",
1789
+ {
1790
+ style: {
1791
+ color: "rgba(255, 255, 255, 0.9)",
1792
+ fontSize: 14,
1793
+ fontWeight: 500,
1794
+ flex: 1
1795
+ },
1796
+ children: [
1797
+ sessions.length > 0 ? `${sessions.length} session${sessions.length > 1 ? "s" : ""}` : "Claude",
1798
+ status && `: ${status.text}`
1799
+ ]
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1803
+ "button",
1804
+ {
1805
+ onClick: handleToggleExpand,
1806
+ style: {
1807
+ background: "#374151",
1808
+ border: "none",
1809
+ borderRadius: 6,
1810
+ padding: "6px 12px",
1811
+ color: "white",
1812
+ fontSize: 12,
1813
+ fontWeight: 500,
1814
+ cursor: "pointer"
1815
+ },
1816
+ children: "Expand"
1817
+ }
1818
+ ),
1819
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1820
+ "button",
1821
+ {
1822
+ onClick: handleDismiss,
1823
+ style: {
1824
+ background: "transparent",
1825
+ border: "none",
1826
+ color: "rgba(255, 255, 255, 0.5)",
1827
+ cursor: "pointer",
1828
+ padding: 4,
1829
+ fontSize: 18,
1830
+ lineHeight: 1
1831
+ },
1832
+ children: "\xD7"
1833
+ }
1834
+ )
1835
+ ]
1836
+ }
1837
+ ),
1838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
1839
+ @keyframes claude-bridge-pulse {
1840
+ 0%, 100% { opacity: 1; }
1841
+ 50% { opacity: 0.5; }
1842
+ }
1843
+ ` })
1844
+ ]
1845
+ }
1846
+ );
1847
+ }
1848
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1849
+ "div",
1850
+ {
1851
+ style: {
1852
+ position: "fixed",
1853
+ bottom: 24,
1854
+ right: 24,
1855
+ zIndex: 1000001,
1856
+ width: size.width,
1857
+ height: size.height,
1858
+ background: "#1e1e2e",
1859
+ borderRadius: 12,
1860
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1861
+ fontFamily: "system-ui, -apple-system, sans-serif",
1862
+ display: "flex",
1863
+ flexDirection: "column",
1864
+ transition: isResizing ? "none" : "opacity 0.3s",
1865
+ overflow: "hidden"
1866
+ },
1867
+ children: [
1868
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1869
+ "div",
1870
+ {
1871
+ onMouseDown: (e) => handleResizeStart(e, "n"),
1872
+ style: {
1873
+ position: "absolute",
1874
+ top: 0,
1875
+ left: 20,
1876
+ right: 20,
1877
+ height: 6,
1878
+ cursor: "ns-resize",
1879
+ zIndex: 10
1880
+ }
1109
1881
  }
1110
1882
  ),
1111
- selectedElement && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1883
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1112
1884
  "div",
1113
1885
  {
1886
+ onMouseDown: (e) => handleResizeStart(e, "w"),
1114
1887
  style: {
1115
- padding: "8px 16px",
1116
- background: "rgba(34, 197, 94, 0.1)",
1117
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
1118
- display: "flex",
1119
- alignItems: "center",
1120
- gap: 8
1121
- },
1122
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1123
- "label",
1124
- {
1125
- style: {
1126
- display: "flex",
1127
- alignItems: "center",
1128
- gap: 8,
1129
- cursor: "pointer",
1130
- flex: 1
1131
- },
1132
- children: [
1133
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1134
- "input",
1135
- {
1136
- type: "checkbox",
1137
- checked: includeElement,
1138
- onChange: (e) => setIncludeElement(e.target.checked),
1139
- style: { cursor: "pointer" }
1140
- }
1141
- ),
1142
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1143
- "span",
1144
- {
1145
- style: {
1146
- color: "rgba(255, 255, 255, 0.7)",
1147
- fontSize: 12
1148
- },
1149
- children: "Include selected element:"
1150
- }
1151
- ),
1152
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1153
- "span",
1154
- {
1155
- style: {
1156
- color: "rgba(255, 255, 255, 0.9)",
1157
- fontSize: 12,
1158
- fontFamily: "ui-monospace, monospace"
1159
- },
1160
- children: [
1161
- elementContext?.componentName || elementContext?.selectedElement.tagName,
1162
- elementContext?.sourceFile && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { color: "rgba(255, 255, 255, 0.5)", marginLeft: 4 }, children: [
1163
- "(",
1164
- elementContext.sourceFile,
1165
- elementContext.sourceLine ? `:${elementContext.sourceLine}` : "",
1166
- ")"
1167
- ] })
1168
- ]
1169
- }
1170
- )
1171
- ]
1172
- }
1173
- )
1888
+ position: "absolute",
1889
+ left: 0,
1890
+ top: 20,
1891
+ bottom: 20,
1892
+ width: 6,
1893
+ cursor: "ew-resize",
1894
+ zIndex: 10
1895
+ }
1174
1896
  }
1175
1897
  ),
1176
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: 16 }, children: [
1177
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1178
- "textarea",
1179
- {
1180
- ref: inputRef,
1181
- value: prompt,
1182
- onChange: (e) => setPrompt(e.target.value),
1183
- onKeyDown: handleKeyDown,
1184
- placeholder: "Ask a question about this page or codebase...",
1185
- disabled: isSubmitting,
1186
- style: {
1187
- width: "100%",
1188
- minHeight: 80,
1189
- maxHeight: 200,
1190
- padding: 12,
1191
- background: "rgba(255, 255, 255, 0.05)",
1192
- border: "1px solid rgba(255, 255, 255, 0.1)",
1193
- borderRadius: 8,
1194
- color: "white",
1195
- fontSize: 14,
1196
- fontFamily: "inherit",
1197
- resize: "vertical",
1198
- outline: "none"
1199
- }
1200
- }
1201
- ),
1202
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1203
- "div",
1204
- {
1205
- style: {
1206
- display: "flex",
1207
- flexWrap: "wrap",
1208
- gap: 8,
1209
- marginTop: 12
1210
- },
1211
- children: [
1212
- "What does this page do?",
1213
- "Where are the backend routes?",
1214
- "How does this component work?",
1215
- "What API calls does this make?"
1216
- ].map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1217
- "button",
1218
- {
1219
- onClick: () => setPrompt(suggestion),
1220
- disabled: isSubmitting,
1221
- style: {
1222
- background: "rgba(255, 255, 255, 0.05)",
1223
- border: "1px solid rgba(255, 255, 255, 0.1)",
1224
- borderRadius: 4,
1225
- padding: "4px 8px",
1226
- color: "rgba(255, 255, 255, 0.7)",
1227
- fontSize: 12,
1228
- cursor: "pointer"
1229
- },
1230
- children: suggestion
1231
- },
1232
- suggestion
1233
- ))
1898
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1899
+ "div",
1900
+ {
1901
+ onMouseDown: (e) => handleResizeStart(e, "nw"),
1902
+ style: {
1903
+ position: "absolute",
1904
+ top: 0,
1905
+ left: 0,
1906
+ width: 20,
1907
+ height: 20,
1908
+ cursor: "nwse-resize",
1909
+ zIndex: 11
1234
1910
  }
1235
- )
1236
- ] }),
1237
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1911
+ }
1912
+ ),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1238
1914
  "div",
1239
1915
  {
1240
1916
  style: {
1241
- padding: "12px 16px",
1242
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
1917
+ borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
1243
1918
  display: "flex",
1244
- justifyContent: "space-between",
1245
- alignItems: "center"
1919
+ alignItems: "center",
1920
+ flexShrink: 0
1246
1921
  },
1247
1922
  children: [
1248
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1249
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1250
- "span",
1923
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1924
+ SessionTabs,
1925
+ {
1926
+ sessions,
1927
+ activeSessionId,
1928
+ onSessionSelect,
1929
+ onSessionClose: handleSessionClose,
1930
+ taskSessionId: activeTask?.sessionId
1931
+ }
1932
+ ),
1933
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4, padding: "4px 8px" }, children: [
1934
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1935
+ "button",
1251
1936
  {
1937
+ onClick: handleToggleExpand,
1938
+ title: "Minimize",
1252
1939
  style: {
1253
- color: "rgba(255, 255, 255, 0.4)",
1254
- fontSize: 12
1940
+ background: "transparent",
1941
+ border: "none",
1942
+ color: "rgba(255, 255, 255, 0.5)",
1943
+ cursor: "pointer",
1944
+ padding: 4,
1945
+ fontSize: 14,
1946
+ lineHeight: 1
1255
1947
  },
1256
- children: [
1257
- navigator.platform.includes("Mac") ? "\u2318" : "Ctrl",
1258
- "+Enter to submit"
1259
- ]
1948
+ children: "\u2212"
1260
1949
  }
1261
1950
  ),
1262
- onSwitchToTaskMode && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1951
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1263
1952
  "button",
1264
1953
  {
1265
- onClick: onSwitchToTaskMode,
1266
- disabled: isSubmitting,
1954
+ onClick: handleOpenZed,
1955
+ title: "Open in Zed Controller",
1267
1956
  style: {
1268
1957
  background: "transparent",
1269
1958
  border: "none",
1270
- color: "#6366f1",
1271
- fontSize: 12,
1959
+ color: "rgba(255, 255, 255, 0.5)",
1272
1960
  cursor: "pointer",
1273
- padding: 0,
1274
- textDecoration: "underline"
1961
+ padding: 4,
1962
+ fontSize: 14,
1963
+ lineHeight: 1
1275
1964
  },
1276
- children: "Make changes instead"
1965
+ children: "\u2197"
1277
1966
  }
1278
- )
1279
- ] }),
1280
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1281
- "button",
1282
- {
1283
- onClick: handleSubmit,
1284
- disabled: !prompt.trim() || isSubmitting,
1285
- style: {
1286
- background: prompt.trim() && !isSubmitting ? "#22c55e" : "rgba(34, 197, 94, 0.3)",
1287
- border: "none",
1288
- borderRadius: 6,
1289
- padding: "8px 16px",
1290
- color: "white",
1291
- fontSize: 14,
1292
- fontWeight: 500,
1293
- cursor: prompt.trim() && !isSubmitting ? "pointer" : "not-allowed",
1967
+ ),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1969
+ "button",
1970
+ {
1971
+ onClick: handleDismiss,
1972
+ disabled: isSaving,
1973
+ title: isSaving ? "Saving changes..." : "Close",
1974
+ style: {
1975
+ background: "transparent",
1976
+ border: "none",
1977
+ color: isSaving ? "rgba(255, 255, 255, 0.3)" : "rgba(255, 255, 255, 0.5)",
1978
+ cursor: isSaving ? "not-allowed" : "pointer",
1979
+ padding: 4,
1980
+ fontSize: 18,
1981
+ lineHeight: 1
1982
+ },
1983
+ children: isSaving ? "..." : "\xD7"
1984
+ }
1985
+ )
1986
+ ] })
1987
+ ]
1988
+ }
1989
+ ),
1990
+ isSaving && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1991
+ "div",
1992
+ {
1993
+ style: {
1994
+ position: "absolute",
1995
+ top: 0,
1996
+ left: 0,
1997
+ right: 0,
1998
+ bottom: 0,
1999
+ background: "rgba(0, 0, 0, 0.7)",
2000
+ display: "flex",
2001
+ flexDirection: "column",
2002
+ alignItems: "center",
2003
+ justifyContent: "center",
2004
+ zIndex: 10,
2005
+ borderRadius: 12
2006
+ },
2007
+ children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#f59e0b", fontSize: 14, fontWeight: 500, marginBottom: 8 }, children: "Saving changes..." }),
2009
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 12 }, children: "Generating commit message with AI" })
2010
+ ]
2011
+ }
2012
+ ),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: activeSessionId ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2014
+ ClaudeTerminal,
2015
+ {
2016
+ serverUrl,
2017
+ token,
2018
+ sessionId: activeSessionId,
2019
+ style: { width: "100%", height: "100%" }
2020
+ }
2021
+ ) : sessions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2022
+ "div",
2023
+ {
2024
+ style: {
2025
+ display: "flex",
2026
+ alignItems: "center",
2027
+ justifyContent: "center",
2028
+ height: "100%",
2029
+ color: "rgba(255, 255, 255, 0.5)",
2030
+ fontSize: 14
2031
+ },
2032
+ children: "Select a session to view"
2033
+ }
2034
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2035
+ "div",
2036
+ {
2037
+ style: {
2038
+ display: "flex",
2039
+ alignItems: "center",
2040
+ justifyContent: "center",
2041
+ height: "100%",
2042
+ color: "rgba(255, 255, 255, 0.5)",
2043
+ fontSize: 14
2044
+ },
2045
+ children: "No active sessions"
2046
+ }
2047
+ ) }),
2048
+ mode !== "terminal" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2049
+ "div",
2050
+ {
2051
+ style: {
2052
+ borderTop: "1px solid rgba(255, 255, 255, 0.1)",
2053
+ padding: 12,
2054
+ flexShrink: 0
2055
+ },
2056
+ children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2058
+ "div",
2059
+ {
2060
+ style: {
1294
2061
  display: "flex",
1295
2062
  alignItems: "center",
1296
- gap: 6
2063
+ gap: 8,
2064
+ marginBottom: 8
1297
2065
  },
1298
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1299
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1300
- "span",
2066
+ children: [
2067
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2068
+ "div",
1301
2069
  {
1302
2070
  style: {
1303
- width: 14,
1304
- height: 14,
1305
- border: "2px solid rgba(255, 255, 255, 0.3)",
1306
- borderTopColor: "white",
2071
+ width: 8,
2072
+ height: 8,
1307
2073
  borderRadius: "50%",
1308
- animation: "claude-bridge-spin 0.8s linear infinite"
2074
+ background: mode === "prompt" ? "#6366f1" : "#22c55e"
1309
2075
  }
1310
2076
  }
1311
2077
  ),
1312
- "Asking..."
1313
- ] }) : "Ask Claude"
2078
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2079
+ "span",
2080
+ {
2081
+ style: {
2082
+ color: "rgba(255, 255, 255, 0.9)",
2083
+ fontSize: 12,
2084
+ fontWeight: 500
2085
+ },
2086
+ children: [
2087
+ mode === "prompt" ? "Task" : "Question",
2088
+ elementContext && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "rgba(255, 255, 255, 0.5)", marginLeft: 8 }, children: [
2089
+ elementContext.componentName || elementContext.selectedElement.tagName,
2090
+ elementContext.sourceFile && ` (${elementContext.sourceFile})`
2091
+ ] })
2092
+ ]
2093
+ }
2094
+ ),
2095
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1 } }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2097
+ "button",
2098
+ {
2099
+ onClick: () => onModeChange(mode === "prompt" ? "question" : "prompt"),
2100
+ style: {
2101
+ background: "transparent",
2102
+ border: "none",
2103
+ color: mode === "prompt" ? "#22c55e" : "#6366f1",
2104
+ fontSize: 11,
2105
+ cursor: "pointer",
2106
+ padding: 0
2107
+ },
2108
+ children: [
2109
+ "Switch to ",
2110
+ mode === "prompt" ? "Question" : "Task"
2111
+ ]
2112
+ }
2113
+ )
2114
+ ]
2115
+ }
2116
+ ),
2117
+ mode === "question" && selectedElement && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2118
+ "label",
2119
+ {
2120
+ style: {
2121
+ display: "flex",
2122
+ alignItems: "center",
2123
+ gap: 8,
2124
+ marginBottom: 8,
2125
+ fontSize: 12,
2126
+ color: "rgba(255, 255, 255, 0.7)",
2127
+ cursor: "pointer"
2128
+ },
2129
+ children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2131
+ "input",
2132
+ {
2133
+ type: "checkbox",
2134
+ checked: includeElement,
2135
+ onChange: (e) => setIncludeElement(e.target.checked)
2136
+ }
2137
+ ),
2138
+ "Include selected element"
2139
+ ]
2140
+ }
2141
+ ),
2142
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative" }, children: [
2143
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2144
+ "textarea",
2145
+ {
2146
+ ref: inputRef,
2147
+ value: prompt,
2148
+ onChange: (e) => setPrompt(e.target.value),
2149
+ onKeyDown: handleKeyDown,
2150
+ placeholder: isVoiceListening ? "Listening..." : mode === "prompt" ? "What would you like Claude to change?" : "Ask a question about this page or codebase...",
2151
+ disabled: isSubmitting || isVoiceListening,
2152
+ style: {
2153
+ width: "100%",
2154
+ minHeight: 60,
2155
+ maxHeight: 120,
2156
+ padding: 10,
2157
+ paddingRight: 44,
2158
+ background: isVoiceListening ? "rgba(239, 68, 68, 0.1)" : "rgba(255, 255, 255, 0.05)",
2159
+ border: isVoiceListening ? "1px solid rgba(239, 68, 68, 0.3)" : "1px solid rgba(255, 255, 255, 0.1)",
2160
+ borderRadius: 8,
2161
+ color: "white",
2162
+ fontSize: 13,
2163
+ fontFamily: "inherit",
2164
+ resize: "none",
2165
+ outline: "none"
2166
+ }
2167
+ }
2168
+ ),
2169
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { position: "absolute", right: 8, top: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2170
+ VoiceInput,
2171
+ {
2172
+ onTranscript: handleVoiceTranscript,
2173
+ onListeningChange: handleVoiceListeningChange,
2174
+ disabled: isSubmitting,
2175
+ size: "small",
2176
+ autoStart: voiceInputActive
2177
+ }
2178
+ ) })
2179
+ ] }),
2180
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2181
+ "div",
2182
+ {
2183
+ style: {
2184
+ display: "flex",
2185
+ justifyContent: "space-between",
2186
+ alignItems: "center",
2187
+ marginTop: 8
2188
+ },
2189
+ children: [
2190
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "rgba(255, 255, 255, 0.4)", fontSize: 11 }, children: [
2191
+ navigator.platform.includes("Mac") ? "\u2318" : "Ctrl",
2192
+ "+Enter to submit"
2193
+ ] }),
2194
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2195
+ "button",
2196
+ {
2197
+ onClick: handleSubmit,
2198
+ disabled: !prompt.trim() || isSubmitting,
2199
+ style: {
2200
+ background: prompt.trim() && !isSubmitting ? mode === "prompt" ? "#6366f1" : "#22c55e" : "rgba(99, 102, 241, 0.3)",
2201
+ border: "none",
2202
+ borderRadius: 6,
2203
+ padding: "6px 14px",
2204
+ color: "white",
2205
+ fontSize: 13,
2206
+ fontWeight: 500,
2207
+ cursor: prompt.trim() && !isSubmitting ? "pointer" : "not-allowed",
2208
+ display: "flex",
2209
+ alignItems: "center",
2210
+ gap: 6
2211
+ },
2212
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2214
+ "span",
2215
+ {
2216
+ style: {
2217
+ width: 12,
2218
+ height: 12,
2219
+ border: "2px solid rgba(255, 255, 255, 0.3)",
2220
+ borderTopColor: "white",
2221
+ borderRadius: "50%",
2222
+ animation: "claude-bridge-spin 0.8s linear infinite"
2223
+ }
2224
+ }
2225
+ ),
2226
+ "Sending..."
2227
+ ] }) : mode === "prompt" ? "Send to Claude" : "Ask Claude"
2228
+ }
2229
+ )
2230
+ ]
1314
2231
  }
1315
2232
  )
1316
2233
  ]
1317
2234
  }
1318
2235
  ),
1319
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
2236
+ activeTask?.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2237
+ "div",
2238
+ {
2239
+ style: {
2240
+ padding: "8px 16px",
2241
+ background: "rgba(239, 68, 68, 0.2)",
2242
+ borderTop: "1px solid rgba(239, 68, 68, 0.3)",
2243
+ color: "#ef4444",
2244
+ fontSize: 12,
2245
+ flexShrink: 0
2246
+ },
2247
+ children: [
2248
+ "Error: ",
2249
+ activeTask.error
2250
+ ]
2251
+ }
2252
+ ),
2253
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
2254
+ @keyframes claude-bridge-pulse {
2255
+ 0%, 100% { opacity: 1; }
2256
+ 50% { opacity: 0.5; }
2257
+ }
1320
2258
  @keyframes claude-bridge-spin {
1321
2259
  to { transform: rotate(360deg); }
1322
2260
  }
@@ -1326,463 +2264,605 @@ function QuestionDialog({
1326
2264
  );
1327
2265
  }
1328
2266
 
1329
- // src/TaskStatusToast.tsx
1330
- var import_react5 = require("react");
1331
-
1332
- // src/ClaudeTerminal.tsx
1333
- var import_react4 = require("react");
1334
- var import_xterm = require("@xterm/xterm");
1335
- var import_addon_fit = require("@xterm/addon-fit");
1336
- var import_addon_webgl = require("@xterm/addon-webgl");
1337
- var import_xterm2 = require("@xterm/xterm/css/xterm.css");
1338
- var import_jsx_runtime4 = require("react/jsx-runtime");
1339
- var XTERM_CRITICAL_CSS = `
1340
- .xterm {
1341
- cursor: text;
1342
- position: relative;
1343
- user-select: none;
1344
- -ms-user-select: none;
1345
- -webkit-user-select: none;
1346
- }
1347
- .xterm.focus,
1348
- .xterm:focus {
1349
- outline: none;
1350
- }
1351
- .xterm .xterm-helpers {
1352
- position: absolute;
1353
- top: 0;
1354
- z-index: 5;
2267
+ // src/hooks/useSessionStorage.ts
2268
+ var import_react8 = require("react");
2269
+ var STORAGE_KEY = "claude-bridge-session";
2270
+ var EXPIRY_MS = 24 * 60 * 60 * 1e3;
2271
+ function useSessionStorage(projectRoot) {
2272
+ const getStoredSessionId = (0, import_react8.useCallback)(() => {
2273
+ if (!projectRoot) return null;
2274
+ try {
2275
+ const stored = localStorage.getItem(STORAGE_KEY);
2276
+ if (!stored) return null;
2277
+ const data = JSON.parse(stored);
2278
+ if (data.projectRoot === projectRoot && Date.now() - data.timestamp < EXPIRY_MS) {
2279
+ return data.lastSessionId;
2280
+ }
2281
+ } catch {
2282
+ }
2283
+ return null;
2284
+ }, [projectRoot]);
2285
+ const setStoredSessionId = (0, import_react8.useCallback)(
2286
+ (sessionId) => {
2287
+ if (sessionId && projectRoot) {
2288
+ const data = {
2289
+ lastSessionId: sessionId,
2290
+ projectRoot,
2291
+ timestamp: Date.now()
2292
+ };
2293
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
2294
+ } else {
2295
+ localStorage.removeItem(STORAGE_KEY);
2296
+ }
2297
+ },
2298
+ [projectRoot]
2299
+ );
2300
+ const clearStoredSession = (0, import_react8.useCallback)(() => {
2301
+ localStorage.removeItem(STORAGE_KEY);
2302
+ }, []);
2303
+ return {
2304
+ getStoredSessionId,
2305
+ setStoredSessionId,
2306
+ clearStoredSession
2307
+ };
1355
2308
  }
1356
- .xterm .xterm-helper-textarea {
1357
- padding: 0;
1358
- border: 0;
1359
- margin: 0;
1360
- position: absolute;
1361
- opacity: 0;
1362
- left: -9999em;
1363
- top: 0;
1364
- width: 0;
1365
- height: 0;
1366
- z-index: -5;
1367
- white-space: nowrap;
1368
- overflow: hidden;
1369
- resize: none;
2309
+
2310
+ // src/ClaudeBridgeProvider.tsx
2311
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2312
+ var initialState = {
2313
+ connected: false,
2314
+ selectionMode: false,
2315
+ questionMode: false,
2316
+ selectedElement: null,
2317
+ activeTask: null,
2318
+ tasks: [],
2319
+ voiceInputPending: false,
2320
+ // Session state
2321
+ sessions: [],
2322
+ activeSessionId: null,
2323
+ panelExpanded: false,
2324
+ panelMode: "terminal"
2325
+ };
2326
+ function reducer(state, action) {
2327
+ switch (action.type) {
2328
+ case "SET_CONNECTED":
2329
+ return { ...state, connected: action.connected };
2330
+ case "SET_SELECTION_MODE":
2331
+ return {
2332
+ ...state,
2333
+ selectionMode: action.enabled,
2334
+ questionMode: action.enabled ? false : state.questionMode,
2335
+ selectedElement: action.enabled ? state.selectedElement : null
2336
+ };
2337
+ case "SET_QUESTION_MODE":
2338
+ return {
2339
+ ...state,
2340
+ questionMode: action.enabled,
2341
+ selectionMode: action.enabled ? false : state.selectionMode
2342
+ };
2343
+ case "SET_SELECTED_ELEMENT":
2344
+ return { ...state, selectedElement: action.element };
2345
+ case "ELEMENT_SELECTED":
2346
+ return {
2347
+ ...state,
2348
+ selectedElement: action.element,
2349
+ selectionMode: false,
2350
+ panelExpanded: true
2351
+ };
2352
+ case "SET_ACTIVE_TASK":
2353
+ return { ...state, activeTask: action.task };
2354
+ case "ADD_TASK":
2355
+ return { ...state, tasks: [...state.tasks, action.task] };
2356
+ case "UPDATE_TASK":
2357
+ return {
2358
+ ...state,
2359
+ tasks: state.tasks.map(
2360
+ (t) => t.id === action.taskId ? { ...t, ...action.updates } : t
2361
+ ),
2362
+ activeTask: state.activeTask?.id === action.taskId ? { ...state.activeTask, ...action.updates } : state.activeTask
2363
+ };
2364
+ case "CLEAR_TASKS":
2365
+ return { ...state, tasks: [], activeTask: null };
2366
+ case "SET_VOICE_INPUT_PENDING":
2367
+ return { ...state, voiceInputPending: action.pending };
2368
+ // Session actions
2369
+ case "SET_SESSIONS":
2370
+ return { ...state, sessions: action.sessions };
2371
+ case "SET_ACTIVE_SESSION":
2372
+ return { ...state, activeSessionId: action.sessionId };
2373
+ case "SET_PANEL_EXPANDED":
2374
+ return { ...state, panelExpanded: action.expanded };
2375
+ case "SET_PANEL_MODE":
2376
+ return { ...state, panelMode: action.mode };
2377
+ default:
2378
+ return state;
2379
+ }
1370
2380
  }
1371
- .xterm .xterm-helper-textarea:focus {
1372
- outline: none;
1373
- }
1374
- .xterm .composition-view {
1375
- background: #000;
1376
- color: #FFF;
1377
- display: none;
1378
- position: absolute;
1379
- white-space: nowrap;
1380
- z-index: 1;
1381
- }
1382
- .xterm .composition-view.active {
1383
- display: block;
1384
- }
1385
- .xterm .xterm-viewport {
1386
- background-color: #000;
1387
- overflow-y: scroll;
1388
- cursor: default;
1389
- position: absolute;
1390
- right: 0;
1391
- left: 0;
1392
- top: 0;
1393
- bottom: 0;
1394
- }
1395
- .xterm .xterm-screen {
1396
- position: relative;
1397
- }
1398
- .xterm .xterm-screen canvas {
1399
- position: absolute;
1400
- left: 0;
1401
- top: 0;
1402
- }
1403
- .xterm-char-measure-element {
1404
- display: inline-block;
1405
- visibility: hidden;
1406
- position: absolute;
1407
- top: 0;
1408
- left: -9999em;
1409
- line-height: normal;
1410
- }
1411
- .xterm.enable-mouse-events {
1412
- cursor: default;
1413
- }
1414
- .xterm .xterm-cursor-pointer {
1415
- cursor: pointer;
1416
- }
1417
- .xterm.xterm-cursor-style-block .xterm-cursor:not(.xterm-cursor-blink),
1418
- .xterm.xterm-cursor-style-bar .xterm-cursor:not(.xterm-cursor-blink),
1419
- .xterm.xterm-cursor-style-underline .xterm-cursor:not(.xterm-cursor-blink) {
1420
- visibility: visible;
1421
- }
1422
- `;
1423
- function ClaudeTerminal({
2381
+ var ClaudeBridgeContext = (0, import_react9.createContext)(null);
2382
+ function ClaudeBridgeProvider({
2383
+ children,
1424
2384
  serverUrl,
1425
2385
  token,
1426
- sessionId,
1427
- onSessionSelect,
1428
- onConnectionChange,
1429
- onSessionsUpdate,
1430
- terminalOptions = {},
1431
- style,
1432
- className
2386
+ enabled = true,
2387
+ projectRoot,
2388
+ shortcut = "Meta+Shift+K",
2389
+ questionShortcut = "Meta+Shift+?",
2390
+ voiceShortcut = "Meta+Shift+V",
2391
+ onTaskCreated,
2392
+ onTaskProgress,
2393
+ onTaskCompleted,
2394
+ onTaskFailed,
2395
+ onError
1433
2396
  }) {
1434
- const containerRef = (0, import_react4.useRef)(null);
1435
- const terminalRef = (0, import_react4.useRef)(null);
1436
- const fitAddonRef = (0, import_react4.useRef)(null);
1437
- const wsRef = (0, import_react4.useRef)(null);
1438
- const handleMessageRef = (0, import_react4.useRef)(null);
1439
- const [connected, setConnected] = (0, import_react4.useState)(false);
1440
- const [sessions, setSessions] = (0, import_react4.useState)([]);
1441
- const [attachedSessionId, setAttachedSessionId] = (0, import_react4.useState)(null);
1442
- const [error, setError] = (0, import_react4.useState)(null);
1443
- (0, import_react4.useEffect)(() => {
1444
- if (!containerRef.current) return;
1445
- const terminal = new import_xterm.Terminal({
1446
- cursorBlink: true,
1447
- cursorStyle: "block",
1448
- fontSize: terminalOptions.fontSize ?? 14,
1449
- fontFamily: terminalOptions.fontFamily ?? 'Menlo, Monaco, "Courier New", monospace',
1450
- theme: {
1451
- background: terminalOptions.theme?.background ?? "#1a1a1a",
1452
- foreground: terminalOptions.theme?.foreground ?? "#f0f0f0",
1453
- cursor: terminalOptions.theme?.cursor ?? "#f0f0f0",
1454
- cursorAccent: "#1a1a1a",
1455
- black: "#1a1a1a",
1456
- red: "#ff5555",
1457
- green: "#50fa7b",
1458
- yellow: "#f1fa8c",
1459
- blue: "#6272a4",
1460
- magenta: "#ff79c6",
1461
- cyan: "#8be9fd",
1462
- white: "#f8f8f2",
1463
- brightBlack: "#6272a4",
1464
- brightRed: "#ff6e6e",
1465
- brightGreen: "#69ff94",
1466
- brightYellow: "#ffffa5",
1467
- brightBlue: "#d6acff",
1468
- brightMagenta: "#ff92df",
1469
- brightCyan: "#a4ffff",
1470
- brightWhite: "#ffffff"
1471
- },
1472
- allowProposedApi: true,
1473
- scrollback: 1e4,
1474
- disableStdin: false,
1475
- allowTransparency: true
1476
- });
1477
- const fitAddon = new import_addon_fit.FitAddon();
1478
- terminal.loadAddon(fitAddon);
1479
- terminal.open(containerRef.current);
1480
- try {
1481
- const webglAddon = new import_addon_webgl.WebglAddon();
1482
- webglAddon.onContextLoss(() => {
1483
- webglAddon.dispose();
1484
- });
1485
- terminal.loadAddon(webglAddon);
1486
- } catch (e) {
1487
- console.warn("[ClaudeTerminal] WebGL addon could not be loaded:", e);
1488
- }
1489
- terminalRef.current = terminal;
1490
- fitAddonRef.current = fitAddon;
1491
- requestAnimationFrame(() => {
1492
- fitAddon.fit();
2397
+ const [state, dispatch] = (0, import_react9.useReducer)(reducer, initialState);
2398
+ const wsClientRef = (0, import_react9.useRef)(null);
2399
+ const pendingTaskResolveRef = (0, import_react9.useRef)(null);
2400
+ const config = {
2401
+ serverUrl,
2402
+ token,
2403
+ enabled,
2404
+ projectRoot,
2405
+ shortcut,
2406
+ questionShortcut,
2407
+ voiceShortcut,
2408
+ onTaskCreated,
2409
+ onTaskProgress,
2410
+ onTaskCompleted,
2411
+ onTaskFailed,
2412
+ onError
2413
+ };
2414
+ (0, import_react9.useEffect)(() => {
2415
+ if (!enabled) return;
2416
+ const client = new BridgeWebSocketClient(config);
2417
+ wsClientRef.current = client;
2418
+ const unsubConnection = client.onConnection((connected) => {
2419
+ dispatch({ type: "SET_CONNECTED", connected });
1493
2420
  });
1494
- const resizeObserver = new ResizeObserver(() => {
1495
- requestAnimationFrame(() => {
1496
- fitAddon.fit();
1497
- if (wsRef.current?.readyState === WebSocket.OPEN && attachedSessionId) {
1498
- wsRef.current.send(JSON.stringify({
1499
- type: "terminal_resize",
1500
- sessionId: attachedSessionId,
1501
- cols: terminal.cols,
1502
- rows: terminal.rows
1503
- }));
1504
- }
1505
- });
2421
+ const unsubMessage = client.onMessage((message) => {
2422
+ handleMessage(message);
1506
2423
  });
1507
- resizeObserver.observe(containerRef.current);
2424
+ client.connect();
1508
2425
  return () => {
1509
- resizeObserver.disconnect();
1510
- terminal.dispose();
1511
- terminalRef.current = null;
1512
- fitAddonRef.current = null;
2426
+ unsubConnection();
2427
+ unsubMessage();
2428
+ client.disconnect();
2429
+ wsClientRef.current = null;
1513
2430
  };
1514
- }, [terminalOptions.fontSize, terminalOptions.fontFamily, terminalOptions.theme]);
1515
- (0, import_react4.useEffect)(() => {
1516
- const terminal = terminalRef.current;
1517
- if (!terminal) return;
1518
- const disposable = terminal.onData((data) => {
1519
- if (wsRef.current?.readyState === WebSocket.OPEN && attachedSessionId) {
1520
- wsRef.current.send(JSON.stringify({
1521
- type: "terminal_input",
1522
- sessionId: attachedSessionId,
1523
- data
1524
- }));
2431
+ }, [enabled, serverUrl, token]);
2432
+ const { getStoredSessionId, setStoredSessionId } = useSessionStorage(projectRoot);
2433
+ const { projectSessions } = useSessions({
2434
+ sessions: state.sessions,
2435
+ projectRoot
2436
+ });
2437
+ (0, import_react9.useEffect)(() => {
2438
+ if (state.connected && wsClientRef.current) {
2439
+ wsClientRef.current.send({ type: "get_sessions" });
2440
+ }
2441
+ }, [state.connected]);
2442
+ (0, import_react9.useEffect)(() => {
2443
+ if (state.connected && projectSessions.length > 0 && !state.activeSessionId) {
2444
+ const storedSessionId = getStoredSessionId();
2445
+ if (storedSessionId) {
2446
+ const sessionExists = projectSessions.some((s) => s.id === storedSessionId);
2447
+ if (sessionExists) {
2448
+ dispatch({ type: "SET_ACTIVE_SESSION", sessionId: storedSessionId });
2449
+ dispatch({ type: "SET_PANEL_EXPANDED", expanded: true });
2450
+ }
1525
2451
  }
1526
- });
1527
- return () => disposable.dispose();
1528
- }, [attachedSessionId]);
1529
- const attachedSessionIdRef = (0, import_react4.useRef)(null);
1530
- attachedSessionIdRef.current = attachedSessionId;
1531
- (0, import_react4.useEffect)(() => {
1532
- handleMessageRef.current = (event) => {
1533
- try {
1534
- const message = JSON.parse(event.data);
1535
- switch (message.type) {
1536
- case "connected":
1537
- wsRef.current?.send(JSON.stringify({ type: "get_sessions" }));
1538
- break;
1539
- case "sessions_list":
1540
- if (message.sessions) {
1541
- setSessions(message.sessions);
1542
- onSessionsUpdate?.(message.sessions);
1543
- }
1544
- break;
1545
- case "terminal_attached":
1546
- if (message.sessionId) {
1547
- setAttachedSessionId(message.sessionId);
1548
- setError(null);
1549
- if (message.scrollback && terminalRef.current) {
1550
- terminalRef.current.write(message.scrollback);
1551
- }
1552
- if (fitAddonRef.current && terminalRef.current) {
1553
- fitAddonRef.current.fit();
1554
- wsRef.current?.send(JSON.stringify({
1555
- type: "terminal_resize",
1556
- sessionId: message.sessionId,
1557
- cols: terminalRef.current.cols,
1558
- rows: terminalRef.current.rows
1559
- }));
1560
- }
2452
+ }
2453
+ }, [state.connected, projectSessions, state.activeSessionId, getStoredSessionId]);
2454
+ (0, import_react9.useEffect)(() => {
2455
+ if (state.activeSessionId) {
2456
+ setStoredSessionId(state.activeSessionId);
2457
+ }
2458
+ }, [state.activeSessionId, setStoredSessionId]);
2459
+ (0, import_react9.useEffect)(() => {
2460
+ if (state.activeTask?.sessionId) {
2461
+ dispatch({ type: "SET_ACTIVE_SESSION", sessionId: state.activeTask.sessionId });
2462
+ dispatch({ type: "SET_PANEL_EXPANDED", expanded: true });
2463
+ }
2464
+ }, [state.activeTask?.sessionId]);
2465
+ const handleMessage = (0, import_react9.useCallback)((message) => {
2466
+ switch (message.type) {
2467
+ case "task_created":
2468
+ if (message.task) {
2469
+ dispatch({ type: "ADD_TASK", task: message.task });
2470
+ dispatch({ type: "SET_ACTIVE_TASK", task: message.task });
2471
+ onTaskCreated?.(message.task.id);
2472
+ pendingTaskResolveRef.current?.(message.task.id);
2473
+ pendingTaskResolveRef.current = null;
2474
+ }
2475
+ break;
2476
+ case "task_progress":
2477
+ if (message.taskId && message.output) {
2478
+ dispatch({
2479
+ type: "UPDATE_TASK",
2480
+ taskId: message.taskId,
2481
+ updates: { status: "running" }
2482
+ });
2483
+ onTaskProgress?.(message.taskId, message.output);
2484
+ }
2485
+ break;
2486
+ case "task_completed":
2487
+ if (message.taskId) {
2488
+ dispatch({
2489
+ type: "UPDATE_TASK",
2490
+ taskId: message.taskId,
2491
+ updates: {
2492
+ status: "completed",
2493
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1561
2494
  }
1562
- break;
1563
- case "terminal_output":
1564
- if (message.data && terminalRef.current && message.sessionId === attachedSessionIdRef.current) {
1565
- terminalRef.current.write(message.data);
2495
+ });
2496
+ onTaskCompleted?.(message.taskId);
2497
+ }
2498
+ break;
2499
+ case "task_failed":
2500
+ if (message.taskId) {
2501
+ dispatch({
2502
+ type: "UPDATE_TASK",
2503
+ taskId: message.taskId,
2504
+ updates: {
2505
+ status: "failed",
2506
+ error: message.error,
2507
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1566
2508
  }
1567
- break;
1568
- case "terminal_detached":
1569
- if (message.sessionId === attachedSessionIdRef.current) {
1570
- setAttachedSessionId(null);
1571
- }
1572
- break;
1573
- case "error":
1574
- setError(message.error || "Unknown error");
1575
- console.error("[ClaudeTerminal] Error:", message.error);
1576
- break;
1577
- case "pong":
1578
- break;
2509
+ });
2510
+ onTaskFailed?.(message.taskId, message.error || "Unknown error");
2511
+ pendingTaskResolveRef.current?.(null);
2512
+ pendingTaskResolveRef.current = null;
1579
2513
  }
1580
- } catch (e) {
1581
- console.error("[ClaudeTerminal] Failed to parse message:", e);
1582
- }
1583
- };
1584
- }, [onSessionsUpdate]);
1585
- (0, import_react4.useEffect)(() => {
1586
- const wsUrl = serverUrl.replace(/^http/, "ws").replace(/\/$/, "");
1587
- const ws = new WebSocket(`${wsUrl}/ws/bridge?token=${encodeURIComponent(token)}`);
1588
- ws.onopen = () => {
1589
- setConnected(true);
1590
- setError(null);
1591
- onConnectionChange?.(true);
1592
- };
1593
- ws.onmessage = (event) => {
1594
- handleMessageRef.current?.(event);
1595
- };
1596
- ws.onclose = () => {
1597
- setConnected(false);
1598
- setAttachedSessionId(null);
1599
- onConnectionChange?.(false);
1600
- };
1601
- ws.onerror = () => {
1602
- setError("WebSocket connection error");
1603
- };
1604
- wsRef.current = ws;
1605
- const pingInterval = setInterval(() => {
1606
- if (ws.readyState === WebSocket.OPEN) {
1607
- ws.send(JSON.stringify({ type: "ping" }));
1608
- }
1609
- }, 3e4);
1610
- return () => {
1611
- clearInterval(pingInterval);
1612
- ws.close();
1613
- wsRef.current = null;
1614
- };
1615
- }, [serverUrl, token, onConnectionChange]);
1616
- (0, import_react4.useEffect)(() => {
1617
- if (sessionId && connected && !attachedSessionId) {
1618
- wsRef.current?.send(JSON.stringify({
1619
- type: "attach_terminal",
1620
- sessionId
1621
- }));
1622
- }
1623
- }, [sessionId, connected, attachedSessionId]);
1624
- (0, import_react4.useEffect)(() => {
1625
- if (attachedSessionId && terminalRef.current && fitAddonRef.current) {
1626
- const timer = setTimeout(() => {
1627
- fitAddonRef.current?.fit();
1628
- terminalRef.current?.focus();
1629
- }, 50);
1630
- return () => clearTimeout(timer);
2514
+ break;
2515
+ case "pong":
2516
+ break;
2517
+ case "sessions_list":
2518
+ if (message.sessions) {
2519
+ dispatch({ type: "SET_SESSIONS", sessions: message.sessions });
2520
+ }
2521
+ break;
2522
+ default:
2523
+ console.log("[ClaudeBridge] Unknown message type:", message.type);
1631
2524
  }
1632
- }, [attachedSessionId]);
1633
- const handleContainerClick = (0, import_react4.useCallback)(() => {
1634
- terminalRef.current?.focus();
2525
+ }, [onTaskCreated, onTaskProgress, onTaskCompleted, onTaskFailed]);
2526
+ const matchesShortcut = (0, import_react9.useCallback)((e, shortcutStr) => {
2527
+ const keys = shortcutStr.split("+");
2528
+ const requiresMeta = keys.includes("Meta");
2529
+ const requiresCtrl = keys.includes("Ctrl");
2530
+ const requiresShift = keys.includes("Shift");
2531
+ const requiresAlt = keys.includes("Alt");
2532
+ const key = keys.find((k) => !["Meta", "Ctrl", "Shift", "Alt"].includes(k));
2533
+ return (requiresMeta ? e.metaKey : true) && (requiresCtrl ? e.ctrlKey : true) && (requiresShift ? e.shiftKey : true) && (requiresAlt ? e.altKey : true) && e.key.toUpperCase() === key?.toUpperCase();
1635
2534
  }, []);
1636
- const handleSelectSession = (0, import_react4.useCallback)((session) => {
1637
- terminalRef.current?.clear();
1638
- if (attachedSessionId) {
1639
- wsRef.current?.send(JSON.stringify({
1640
- type: "detach_terminal",
1641
- sessionId: attachedSessionId
1642
- }));
1643
- }
1644
- wsRef.current?.send(JSON.stringify({
1645
- type: "attach_terminal",
1646
- sessionId: session.id
1647
- }));
1648
- onSessionSelect?.(session);
1649
- }, [attachedSessionId, onSessionSelect]);
1650
- if (!sessionId && !attachedSessionId) {
1651
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1652
- "div",
1653
- {
1654
- className,
1655
- style: {
1656
- background: "#1a1a1a",
1657
- color: "#f0f0f0",
1658
- padding: "20px",
1659
- fontFamily: 'Menlo, Monaco, "Courier New", monospace',
1660
- ...style
1661
- },
1662
- children: !connected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "Connecting to server..." }) : error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "#ff5555" }, children: [
1663
- "Error: ",
1664
- error
1665
- ] }) : sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "No Claude sessions available" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1666
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginBottom: "10px", fontSize: "14px" }, children: "Select a Claude session:" }),
1667
- sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1668
- "div",
1669
- {
1670
- onClick: () => handleSelectSession(session),
1671
- style: {
1672
- padding: "10px",
1673
- margin: "5px 0",
1674
- background: "#2a2a2a",
1675
- borderRadius: "4px",
1676
- cursor: "pointer",
1677
- border: "1px solid #3a3a3a"
1678
- },
1679
- onMouseOver: (e) => {
1680
- e.currentTarget.style.background = "#3a3a3a";
1681
- },
1682
- onMouseOut: (e) => {
1683
- e.currentTarget.style.background = "#2a2a2a";
1684
- },
1685
- children: [
1686
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: "bold" }, children: session.displayName || session.name }),
1687
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "12px", color: "#888", marginTop: "4px" }, children: session.workingDir })
1688
- ]
1689
- },
1690
- session.id
1691
- ))
1692
- ] })
1693
- }
1694
- );
1695
- }
1696
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1697
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: XTERM_CRITICAL_CSS }),
1698
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1699
- "div",
1700
- {
1701
- ref: containerRef,
1702
- className,
1703
- onClick: handleContainerClick,
1704
- style: {
1705
- width: "100%",
1706
- height: "100%",
1707
- ...style
2535
+ (0, import_react9.useEffect)(() => {
2536
+ if (!enabled) return;
2537
+ const handleKeyDown = (e) => {
2538
+ if (matchesShortcut(e, shortcut)) {
2539
+ e.preventDefault();
2540
+ if (!state.selectionMode) {
2541
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
1708
2542
  }
2543
+ dispatch({ type: "SET_SELECTION_MODE", enabled: !state.selectionMode });
2544
+ return;
1709
2545
  }
1710
- )
1711
- ] });
1712
- }
1713
-
1714
- // src/TaskStatusToast.tsx
1715
- var import_jsx_runtime5 = require("react/jsx-runtime");
1716
- var MIN_WIDTH = 400;
1717
- var MIN_HEIGHT = 300;
1718
- var DEFAULT_WIDTH = 700;
1719
- var DEFAULT_HEIGHT = 500;
1720
- function TaskStatusToast({
1721
- task,
1722
- serverUrl,
1723
- token,
1724
- onDismiss,
1725
- killOnClose = true
1726
- }) {
1727
- const [visible, setVisible] = (0, import_react5.useState)(false);
1728
- const [expanded, setExpanded] = (0, import_react5.useState)(false);
1729
- const [size, setSize] = (0, import_react5.useState)({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT });
1730
- const [isResizing, setIsResizing] = (0, import_react5.useState)(false);
1731
- const [isSaving, setIsSaving] = (0, import_react5.useState)(false);
1732
- const resizeRef = (0, import_react5.useRef)(null);
1733
- (0, import_react5.useEffect)(() => {
1734
- if (task) {
1735
- setVisible(true);
1736
- if (task.sessionId && (task.status === "running" || task.status === "starting")) {
1737
- setExpanded(true);
2546
+ if (matchesShortcut(e, questionShortcut)) {
2547
+ e.preventDefault();
2548
+ if (!state.questionMode) {
2549
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
2550
+ }
2551
+ dispatch({ type: "SET_QUESTION_MODE", enabled: !state.questionMode });
2552
+ return;
1738
2553
  }
1739
- }
1740
- }, [task, task?.sessionId, task?.status]);
1741
- (0, import_react5.useEffect)(() => {
1742
- if (task && !expanded && (task.status === "completed" || task.status === "failed")) {
1743
- const timer = setTimeout(() => {
1744
- setVisible(false);
1745
- setTimeout(onDismiss, 300);
1746
- }, 1e4);
1747
- return () => clearTimeout(timer);
1748
- }
1749
- }, [task?.status, expanded, onDismiss]);
1750
- const handleResizeStart = (0, import_react5.useCallback)((e, direction) => {
1751
- e.preventDefault();
1752
- e.stopPropagation();
1753
- setIsResizing(true);
1754
- resizeRef.current = {
1755
- startX: e.clientX,
1756
- startY: e.clientY,
1757
- startWidth: size.width,
1758
- startHeight: size.height
1759
- };
1760
- const handleMouseMove = (moveEvent) => {
1761
- if (!resizeRef.current) return;
1762
- let newWidth = resizeRef.current.startWidth;
1763
- let newHeight = resizeRef.current.startHeight;
1764
- if (direction.includes("w")) {
1765
- newWidth = resizeRef.current.startWidth - (moveEvent.clientX - resizeRef.current.startX);
2554
+ if (matchesShortcut(e, voiceShortcut)) {
2555
+ e.preventDefault();
2556
+ if (!state.selectionMode && !state.selectedElement) {
2557
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
2558
+ dispatch({ type: "SET_SELECTION_MODE", enabled: true });
2559
+ }
2560
+ dispatch({ type: "SET_VOICE_INPUT_PENDING", pending: true });
2561
+ return;
1766
2562
  }
1767
- if (direction.includes("n")) {
1768
- newHeight = resizeRef.current.startHeight - (moveEvent.clientY - resizeRef.current.startY);
2563
+ if (e.key === "Escape") {
2564
+ if (state.selectionMode) {
2565
+ dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2566
+ }
2567
+ if (state.questionMode) {
2568
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2569
+ }
2570
+ dispatch({ type: "SET_VOICE_INPUT_PENDING", pending: false });
1769
2571
  }
1770
- setSize({
1771
- width: Math.max(MIN_WIDTH, newWidth),
1772
- height: Math.max(MIN_HEIGHT, newHeight)
1773
- });
1774
2572
  };
1775
- const handleMouseUp = () => {
1776
- setIsResizing(false);
1777
- resizeRef.current = null;
1778
- document.removeEventListener("mousemove", handleMouseMove);
1779
- document.removeEventListener("mouseup", handleMouseUp);
2573
+ window.addEventListener("keydown", handleKeyDown);
2574
+ return () => window.removeEventListener("keydown", handleKeyDown);
2575
+ }, [enabled, shortcut, questionShortcut, voiceShortcut, state.selectionMode, state.questionMode, state.selectedElement, matchesShortcut]);
2576
+ const enableSelectionMode = (0, import_react9.useCallback)(() => {
2577
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
2578
+ dispatch({ type: "SET_SELECTION_MODE", enabled: true });
2579
+ }, []);
2580
+ const disableSelectionMode = (0, import_react9.useCallback)(() => {
2581
+ dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2582
+ }, []);
2583
+ const enableQuestionMode = (0, import_react9.useCallback)(() => {
2584
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
2585
+ dispatch({ type: "SET_QUESTION_MODE", enabled: true });
2586
+ }, []);
2587
+ const disableQuestionMode = (0, import_react9.useCallback)(() => {
2588
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2589
+ }, []);
2590
+ const submitTask = (0, import_react9.useCallback)(async (prompt) => {
2591
+ if (!state.selectedElement || !wsClientRef.current?.isConnected) {
2592
+ return null;
2593
+ }
2594
+ const context = captureContext(state.selectedElement);
2595
+ console.log("[ClaudeBridge] submitTask captured context:", {
2596
+ sourceFile: context.sourceFile,
2597
+ sourceLine: context.sourceLine,
2598
+ componentName: context.componentName,
2599
+ element: state.selectedElement.tagName
2600
+ });
2601
+ const screenshot = await captureElementScreenshot(state.selectedElement);
2602
+ const fullContext = {
2603
+ ...context,
2604
+ screenshot
1780
2605
  };
1781
- document.addEventListener("mousemove", handleMouseMove);
1782
- document.addEventListener("mouseup", handleMouseUp);
1783
- }, [size]);
1784
- const killSession = (0, import_react5.useCallback)((sessionId, taskPrompt) => {
1785
- return new Promise((resolve) => {
2606
+ const taskIdPromise = new Promise((resolve) => {
2607
+ pendingTaskResolveRef.current = resolve;
2608
+ setTimeout(() => {
2609
+ if (pendingTaskResolveRef.current === resolve) {
2610
+ pendingTaskResolveRef.current = null;
2611
+ resolve(null);
2612
+ }
2613
+ }, 1e4);
2614
+ });
2615
+ const sent = wsClientRef.current.createTask(prompt, fullContext);
2616
+ if (!sent) {
2617
+ pendingTaskResolveRef.current = null;
2618
+ return null;
2619
+ }
2620
+ dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2621
+ return taskIdPromise;
2622
+ }, [state.selectedElement]);
2623
+ const continueTask = (0, import_react9.useCallback)(async (taskId, prompt) => {
2624
+ if (!wsClientRef.current?.isConnected) {
2625
+ throw new Error("Not connected");
2626
+ }
2627
+ let context = void 0;
2628
+ if (state.selectedElement) {
2629
+ const baseContext = captureContext(state.selectedElement);
2630
+ const screenshot = await captureElementScreenshot(state.selectedElement);
2631
+ context = { ...baseContext, screenshot };
2632
+ }
2633
+ const sent = wsClientRef.current.continueTask(taskId, prompt, context);
2634
+ if (!sent) {
2635
+ throw new Error("Failed to send continue task message");
2636
+ }
2637
+ }, [state.selectedElement]);
2638
+ const submitQuestion = (0, import_react9.useCallback)(async (prompt, includeSelectedElement = false) => {
2639
+ if (!wsClientRef.current?.isConnected) {
2640
+ return null;
2641
+ }
2642
+ let context;
2643
+ if (includeSelectedElement && state.selectedElement) {
2644
+ const baseContext = captureContext(state.selectedElement);
2645
+ const screenshot = await captureElementScreenshot(state.selectedElement);
2646
+ context = { ...baseContext, screenshot };
2647
+ } else {
2648
+ context = capturePageContext();
2649
+ }
2650
+ const questionPrompt = `[QUESTION - No code changes expected, just provide information]
2651
+
2652
+ ${prompt}`;
2653
+ const taskIdPromise = new Promise((resolve) => {
2654
+ pendingTaskResolveRef.current = resolve;
2655
+ setTimeout(() => {
2656
+ if (pendingTaskResolveRef.current === resolve) {
2657
+ pendingTaskResolveRef.current = null;
2658
+ resolve(null);
2659
+ }
2660
+ }, 1e4);
2661
+ });
2662
+ const sent = wsClientRef.current.createTask(questionPrompt, context);
2663
+ if (!sent) {
2664
+ pendingTaskResolveRef.current = null;
2665
+ return null;
2666
+ }
2667
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2668
+ return taskIdPromise;
2669
+ }, [state.selectedElement]);
2670
+ const handleElementSelect = (0, import_react9.useCallback)((element) => {
2671
+ dispatch({ type: "ELEMENT_SELECTED", element });
2672
+ }, []);
2673
+ const handlePromptSubmit = (0, import_react9.useCallback)(async (prompt) => {
2674
+ await submitTask(prompt);
2675
+ }, [submitTask]);
2676
+ const handlePromptClose = (0, import_react9.useCallback)(() => {
2677
+ dispatch({ type: "SET_SELECTED_ELEMENT", element: null });
2678
+ }, []);
2679
+ const handleQuestionSubmit = (0, import_react9.useCallback)(async (prompt, includeElement) => {
2680
+ await submitQuestion(prompt, includeElement);
2681
+ }, [submitQuestion]);
2682
+ const handleQuestionClose = (0, import_react9.useCallback)(() => {
2683
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2684
+ }, []);
2685
+ const handleSwitchToQuestionMode = (0, import_react9.useCallback)(() => {
2686
+ dispatch({ type: "SET_QUESTION_MODE", enabled: true });
2687
+ }, []);
2688
+ const handleSwitchToTaskMode = (0, import_react9.useCallback)(() => {
2689
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2690
+ if (!state.selectedElement) {
2691
+ dispatch({ type: "SET_SELECTION_MODE", enabled: true });
2692
+ }
2693
+ }, [state.selectedElement]);
2694
+ const handleToastDismiss = (0, import_react9.useCallback)(() => {
2695
+ dispatch({ type: "SET_ACTIVE_TASK", task: null });
2696
+ }, []);
2697
+ const handleSessionClose = (0, import_react9.useCallback)((sessionId) => {
2698
+ if (state.activeSessionId === sessionId) {
2699
+ dispatch({ type: "SET_ACTIVE_SESSION", sessionId: null });
2700
+ }
2701
+ }, [state.activeSessionId]);
2702
+ const handleVoiceInputStart = (0, import_react9.useCallback)(() => {
2703
+ dispatch({ type: "SET_VOICE_INPUT_PENDING", pending: false });
2704
+ }, []);
2705
+ const setActiveSession = (0, import_react9.useCallback)((sessionId) => {
2706
+ dispatch({ type: "SET_ACTIVE_SESSION", sessionId });
2707
+ }, []);
2708
+ const setPanelExpanded = (0, import_react9.useCallback)((expanded) => {
2709
+ dispatch({ type: "SET_PANEL_EXPANDED", expanded });
2710
+ }, []);
2711
+ const setPanelMode = (0, import_react9.useCallback)((mode) => {
2712
+ dispatch({ type: "SET_PANEL_MODE", mode });
2713
+ }, []);
2714
+ const handlePanelModeChange = (0, import_react9.useCallback)((mode) => {
2715
+ dispatch({ type: "SET_PANEL_MODE", mode });
2716
+ if (mode === "prompt") {
2717
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2718
+ } else if (mode === "question") {
2719
+ dispatch({ type: "SET_QUESTION_MODE", enabled: true });
2720
+ } else {
2721
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2722
+ dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2723
+ }
2724
+ }, []);
2725
+ const handlePanelClose = (0, import_react9.useCallback)(() => {
2726
+ dispatch({ type: "SET_PANEL_EXPANDED", expanded: false });
2727
+ dispatch({ type: "SET_PANEL_MODE", mode: "terminal" });
2728
+ dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2729
+ dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2730
+ dispatch({ type: "SET_SELECTED_ELEMENT", element: null });
2731
+ }, []);
2732
+ const effectivePanelMode = state.questionMode ? "question" : state.selectedElement !== null ? "prompt" : "terminal";
2733
+ const contextValue = {
2734
+ state,
2735
+ config,
2736
+ enableSelectionMode,
2737
+ disableSelectionMode,
2738
+ enableQuestionMode,
2739
+ disableQuestionMode,
2740
+ submitTask,
2741
+ submitQuestion,
2742
+ continueTask,
2743
+ setActiveSession,
2744
+ setPanelExpanded,
2745
+ setPanelMode
2746
+ };
2747
+ const shouldShowPanel = projectSessions.length > 0 || state.activeTask !== null || effectivePanelMode !== "terminal";
2748
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ClaudeBridgeContext.Provider, { value: contextValue, children: [
2749
+ children,
2750
+ enabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2751
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2752
+ SelectionOverlay,
2753
+ {
2754
+ active: state.selectionMode,
2755
+ onElementSelect: handleElementSelect,
2756
+ selectedElement: state.selectedElement
2757
+ }
2758
+ ),
2759
+ shouldShowPanel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2760
+ SessionPanel,
2761
+ {
2762
+ sessions: projectSessions,
2763
+ activeSessionId: state.activeSessionId,
2764
+ onSessionSelect: setActiveSession,
2765
+ onSessionClose: handleSessionClose,
2766
+ serverUrl,
2767
+ token,
2768
+ expanded: state.panelExpanded || effectivePanelMode !== "terminal",
2769
+ onExpandedChange: setPanelExpanded,
2770
+ selectedElement: state.selectedElement,
2771
+ mode: effectivePanelMode,
2772
+ onModeChange: handlePanelModeChange,
2773
+ onPromptSubmit: handlePromptSubmit,
2774
+ onQuestionSubmit: handleQuestionSubmit,
2775
+ onClose: handlePanelClose,
2776
+ isSubmitting: state.activeTask?.status === "starting" || state.activeTask?.status === "running",
2777
+ voiceInputActive: state.voiceInputPending,
2778
+ onVoiceInputStart: handleVoiceInputStart,
2779
+ activeTask: state.activeTask
2780
+ }
2781
+ )
2782
+ ] })
2783
+ ] });
2784
+ }
2785
+ function useClaudeBridge() {
2786
+ const context = (0, import_react9.useContext)(ClaudeBridgeContext);
2787
+ if (!context) {
2788
+ throw new Error("useClaudeBridge must be used within a ClaudeBridgeProvider");
2789
+ }
2790
+ return context;
2791
+ }
2792
+
2793
+ // src/TaskStatusToast.tsx
2794
+ var import_react10 = require("react");
2795
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2796
+ var MIN_WIDTH2 = 400;
2797
+ var MIN_HEIGHT2 = 300;
2798
+ var DEFAULT_WIDTH2 = 700;
2799
+ var DEFAULT_HEIGHT2 = 500;
2800
+ function TaskStatusToast({
2801
+ task,
2802
+ serverUrl,
2803
+ token,
2804
+ onDismiss,
2805
+ killOnClose = true
2806
+ }) {
2807
+ const [visible, setVisible] = (0, import_react10.useState)(false);
2808
+ const [expanded, setExpanded] = (0, import_react10.useState)(false);
2809
+ const [size, setSize] = (0, import_react10.useState)({ width: DEFAULT_WIDTH2, height: DEFAULT_HEIGHT2 });
2810
+ const [isResizing, setIsResizing] = (0, import_react10.useState)(false);
2811
+ const [isSaving, setIsSaving] = (0, import_react10.useState)(false);
2812
+ const resizeRef = (0, import_react10.useRef)(null);
2813
+ (0, import_react10.useEffect)(() => {
2814
+ if (task) {
2815
+ setVisible(true);
2816
+ if (task.sessionId && (task.status === "running" || task.status === "starting")) {
2817
+ setExpanded(true);
2818
+ }
2819
+ }
2820
+ }, [task, task?.sessionId, task?.status]);
2821
+ (0, import_react10.useEffect)(() => {
2822
+ if (task && !expanded && (task.status === "completed" || task.status === "failed")) {
2823
+ const timer = setTimeout(() => {
2824
+ setVisible(false);
2825
+ setTimeout(onDismiss, 300);
2826
+ }, 1e4);
2827
+ return () => clearTimeout(timer);
2828
+ }
2829
+ }, [task?.status, expanded, onDismiss]);
2830
+ const handleResizeStart = (0, import_react10.useCallback)((e, direction) => {
2831
+ e.preventDefault();
2832
+ e.stopPropagation();
2833
+ setIsResizing(true);
2834
+ resizeRef.current = {
2835
+ startX: e.clientX,
2836
+ startY: e.clientY,
2837
+ startWidth: size.width,
2838
+ startHeight: size.height
2839
+ };
2840
+ const handleMouseMove = (moveEvent) => {
2841
+ if (!resizeRef.current) return;
2842
+ let newWidth = resizeRef.current.startWidth;
2843
+ let newHeight = resizeRef.current.startHeight;
2844
+ if (direction.includes("w")) {
2845
+ newWidth = resizeRef.current.startWidth - (moveEvent.clientX - resizeRef.current.startX);
2846
+ }
2847
+ if (direction.includes("n")) {
2848
+ newHeight = resizeRef.current.startHeight - (moveEvent.clientY - resizeRef.current.startY);
2849
+ }
2850
+ setSize({
2851
+ width: Math.max(MIN_WIDTH2, newWidth),
2852
+ height: Math.max(MIN_HEIGHT2, newHeight)
2853
+ });
2854
+ };
2855
+ const handleMouseUp = () => {
2856
+ setIsResizing(false);
2857
+ resizeRef.current = null;
2858
+ document.removeEventListener("mousemove", handleMouseMove);
2859
+ document.removeEventListener("mouseup", handleMouseUp);
2860
+ };
2861
+ document.addEventListener("mousemove", handleMouseMove);
2862
+ document.addEventListener("mouseup", handleMouseUp);
2863
+ }, [size]);
2864
+ const killSession = (0, import_react10.useCallback)((sessionId, taskPrompt) => {
2865
+ return new Promise((resolve) => {
1786
2866
  const wsUrl = serverUrl.replace(/^http/, "ws").replace(/\/$/, "");
1787
2867
  const ws = new WebSocket(`${wsUrl}/ws/bridge?token=${encodeURIComponent(token)}`);
1788
2868
  let resolved = false;
@@ -1823,7 +2903,7 @@ function TaskStatusToast({
1823
2903
  setTimeout(cleanup, 35e3);
1824
2904
  });
1825
2905
  }, [serverUrl, token]);
1826
- const handleDismiss = (0, import_react5.useCallback)(async () => {
2906
+ const handleDismiss = (0, import_react10.useCallback)(async () => {
1827
2907
  if (killOnClose && task?.sessionId) {
1828
2908
  setIsSaving(true);
1829
2909
  try {
@@ -1836,7 +2916,7 @@ function TaskStatusToast({
1836
2916
  setExpanded(false);
1837
2917
  setTimeout(onDismiss, 300);
1838
2918
  }, [killOnClose, task?.sessionId, task?.prompt, killSession, onDismiss]);
1839
- const handleToggleExpand = (0, import_react5.useCallback)(() => {
2919
+ const handleToggleExpand = (0, import_react10.useCallback)(() => {
1840
2920
  setExpanded(!expanded);
1841
2921
  }, [expanded]);
1842
2922
  if (!task || !visible) return null;
@@ -1854,7 +2934,7 @@ function TaskStatusToast({
1854
2934
  window.open(zedUrl, "_blank");
1855
2935
  };
1856
2936
  if (!expanded) {
1857
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2937
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1858
2938
  "div",
1859
2939
  {
1860
2940
  style: {
@@ -1873,7 +2953,7 @@ function TaskStatusToast({
1873
2953
  transition: "opacity 0.3s, transform 0.3s"
1874
2954
  },
1875
2955
  children: [
1876
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2956
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1877
2957
  "div",
1878
2958
  {
1879
2959
  style: {
@@ -1884,7 +2964,7 @@ function TaskStatusToast({
1884
2964
  gap: 10
1885
2965
  },
1886
2966
  children: [
1887
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1888
2968
  "div",
1889
2969
  {
1890
2970
  style: {
@@ -1896,7 +2976,7 @@ function TaskStatusToast({
1896
2976
  }
1897
2977
  }
1898
2978
  ),
1899
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2979
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1900
2980
  "span",
1901
2981
  {
1902
2982
  style: {
@@ -1911,7 +2991,7 @@ function TaskStatusToast({
1911
2991
  ]
1912
2992
  }
1913
2993
  ),
1914
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2994
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1915
2995
  "button",
1916
2996
  {
1917
2997
  onClick: handleDismiss,
@@ -1931,8 +3011,8 @@ function TaskStatusToast({
1931
3011
  ]
1932
3012
  }
1933
3013
  ),
1934
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "12px 16px" }, children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3014
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { padding: "12px 16px" }, children: [
3015
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1936
3016
  "p",
1937
3017
  {
1938
3018
  style: {
@@ -1949,7 +3029,7 @@ function TaskStatusToast({
1949
3029
  children: task.prompt
1950
3030
  }
1951
3031
  ),
1952
- task.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3032
+ task.error && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1953
3033
  "p",
1954
3034
  {
1955
3035
  style: {
@@ -1964,7 +3044,7 @@ function TaskStatusToast({
1964
3044
  }
1965
3045
  )
1966
3046
  ] }),
1967
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3047
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1968
3048
  "div",
1969
3049
  {
1970
3050
  style: {
@@ -1975,7 +3055,7 @@ function TaskStatusToast({
1975
3055
  gap: 8
1976
3056
  },
1977
3057
  children: [
1978
- task.sessionId && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3058
+ task.sessionId && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1979
3059
  "button",
1980
3060
  {
1981
3061
  onClick: handleToggleExpand,
@@ -1995,7 +3075,7 @@ function TaskStatusToast({
1995
3075
  children: "Show Terminal"
1996
3076
  }
1997
3077
  ),
1998
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3078
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1999
3079
  "button",
2000
3080
  {
2001
3081
  onClick: handleOpenZed,
@@ -2014,14 +3094,14 @@ function TaskStatusToast({
2014
3094
  },
2015
3095
  children: [
2016
3096
  "Open in Zed Controller",
2017
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { fontSize: 11 }, children: "\u2197" })
3097
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 11 }, children: "\u2197" })
2018
3098
  ]
2019
3099
  }
2020
3100
  )
2021
3101
  ]
2022
3102
  }
2023
3103
  ),
2024
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
3104
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
2025
3105
  @keyframes claude-bridge-pulse {
2026
3106
  0%, 100% { opacity: 1; }
2027
3107
  50% { opacity: 0.5; }
@@ -2031,7 +3111,7 @@ function TaskStatusToast({
2031
3111
  }
2032
3112
  );
2033
3113
  }
2034
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3114
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2035
3115
  "div",
2036
3116
  {
2037
3117
  style: {
@@ -2052,7 +3132,7 @@ function TaskStatusToast({
2052
3132
  overflow: "hidden"
2053
3133
  },
2054
3134
  children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3135
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2056
3136
  "div",
2057
3137
  {
2058
3138
  onMouseDown: (e) => handleResizeStart(e, "n"),
@@ -2067,7 +3147,7 @@ function TaskStatusToast({
2067
3147
  }
2068
3148
  }
2069
3149
  ),
2070
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3150
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2071
3151
  "div",
2072
3152
  {
2073
3153
  onMouseDown: (e) => handleResizeStart(e, "w"),
@@ -2082,7 +3162,7 @@ function TaskStatusToast({
2082
3162
  }
2083
3163
  }
2084
3164
  ),
2085
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3165
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2086
3166
  "div",
2087
3167
  {
2088
3168
  onMouseDown: (e) => handleResizeStart(e, "nw"),
@@ -2097,7 +3177,7 @@ function TaskStatusToast({
2097
3177
  }
2098
3178
  }
2099
3179
  ),
2100
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3180
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2101
3181
  "div",
2102
3182
  {
2103
3183
  style: {
@@ -2109,7 +3189,7 @@ function TaskStatusToast({
2109
3189
  flexShrink: 0
2110
3190
  },
2111
3191
  children: [
2112
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3192
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2113
3193
  "div",
2114
3194
  {
2115
3195
  style: {
@@ -2121,7 +3201,7 @@ function TaskStatusToast({
2121
3201
  }
2122
3202
  }
2123
3203
  ),
2124
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3204
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2125
3205
  "span",
2126
3206
  {
2127
3207
  style: {
@@ -2142,7 +3222,7 @@ function TaskStatusToast({
2142
3222
  ]
2143
3223
  }
2144
3224
  ),
2145
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3225
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2146
3226
  "button",
2147
3227
  {
2148
3228
  onClick: handleToggleExpand,
@@ -2159,7 +3239,7 @@ function TaskStatusToast({
2159
3239
  children: "\u2212"
2160
3240
  }
2161
3241
  ),
2162
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3242
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2163
3243
  "button",
2164
3244
  {
2165
3245
  onClick: handleOpenZed,
@@ -2176,7 +3256,7 @@ function TaskStatusToast({
2176
3256
  children: "\u2197"
2177
3257
  }
2178
3258
  ),
2179
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3259
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2180
3260
  "button",
2181
3261
  {
2182
3262
  onClick: handleDismiss,
@@ -2197,7 +3277,7 @@ function TaskStatusToast({
2197
3277
  ]
2198
3278
  }
2199
3279
  ),
2200
- isSaving && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3280
+ isSaving && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2201
3281
  "div",
2202
3282
  {
2203
3283
  style: {
@@ -2215,7 +3295,7 @@ function TaskStatusToast({
2215
3295
  borderRadius: 12
2216
3296
  },
2217
3297
  children: [
2218
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3298
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2219
3299
  "div",
2220
3300
  {
2221
3301
  style: {
@@ -2227,7 +3307,7 @@ function TaskStatusToast({
2227
3307
  children: "Saving changes..."
2228
3308
  }
2229
3309
  ),
2230
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3310
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2231
3311
  "div",
2232
3312
  {
2233
3313
  style: {
@@ -2240,7 +3320,7 @@ function TaskStatusToast({
2240
3320
  ]
2241
3321
  }
2242
3322
  ),
2243
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: task.sessionId ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3323
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: task.sessionId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2244
3324
  ClaudeTerminal,
2245
3325
  {
2246
3326
  serverUrl,
@@ -2248,7 +3328,7 @@ function TaskStatusToast({
2248
3328
  sessionId: task.sessionId,
2249
3329
  style: { width: "100%", height: "100%" }
2250
3330
  }
2251
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3331
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2252
3332
  "div",
2253
3333
  {
2254
3334
  style: {
@@ -2261,7 +3341,7 @@ function TaskStatusToast({
2261
3341
  children: "Waiting for session..."
2262
3342
  }
2263
3343
  ) }),
2264
- task.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3344
+ task.error && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2265
3345
  "div",
2266
3346
  {
2267
3347
  style: {
@@ -2278,7 +3358,7 @@ function TaskStatusToast({
2278
3358
  ]
2279
3359
  }
2280
3360
  ),
2281
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
3361
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
2282
3362
  @keyframes claude-bridge-pulse {
2283
3363
  0%, 100% { opacity: 1; }
2284
3364
  50% { opacity: 0.5; }
@@ -2288,393 +3368,22 @@ function TaskStatusToast({
2288
3368
  }
2289
3369
  );
2290
3370
  }
2291
-
2292
- // src/ClaudeBridgeProvider.tsx
2293
- var import_jsx_runtime6 = require("react/jsx-runtime");
2294
- var initialState = {
2295
- connected: false,
2296
- selectionMode: false,
2297
- questionMode: false,
2298
- selectedElement: null,
2299
- activeTask: null,
2300
- tasks: []
2301
- };
2302
- function reducer(state, action) {
2303
- switch (action.type) {
2304
- case "SET_CONNECTED":
2305
- return { ...state, connected: action.connected };
2306
- case "SET_SELECTION_MODE":
2307
- return {
2308
- ...state,
2309
- selectionMode: action.enabled,
2310
- questionMode: action.enabled ? false : state.questionMode,
2311
- // Turn off question mode when entering selection mode
2312
- selectedElement: action.enabled ? state.selectedElement : null
2313
- };
2314
- case "SET_QUESTION_MODE":
2315
- return {
2316
- ...state,
2317
- questionMode: action.enabled,
2318
- selectionMode: action.enabled ? false : state.selectionMode
2319
- // Turn off selection mode when entering question mode
2320
- };
2321
- case "SET_SELECTED_ELEMENT":
2322
- return { ...state, selectedElement: action.element };
2323
- case "SET_ACTIVE_TASK":
2324
- return { ...state, activeTask: action.task };
2325
- case "ADD_TASK":
2326
- return { ...state, tasks: [...state.tasks, action.task] };
2327
- case "UPDATE_TASK":
2328
- return {
2329
- ...state,
2330
- tasks: state.tasks.map(
2331
- (t) => t.id === action.taskId ? { ...t, ...action.updates } : t
2332
- ),
2333
- activeTask: state.activeTask?.id === action.taskId ? { ...state.activeTask, ...action.updates } : state.activeTask
2334
- };
2335
- case "CLEAR_TASKS":
2336
- return { ...state, tasks: [], activeTask: null };
2337
- default:
2338
- return state;
2339
- }
2340
- }
2341
- var ClaudeBridgeContext = (0, import_react6.createContext)(null);
2342
- function ClaudeBridgeProvider({
2343
- children,
2344
- serverUrl,
2345
- token,
2346
- enabled = true,
2347
- projectRoot,
2348
- shortcut = "Meta+Shift+K",
2349
- questionShortcut = "Meta+Shift+?",
2350
- onTaskCreated,
2351
- onTaskProgress,
2352
- onTaskCompleted,
2353
- onTaskFailed,
2354
- onError
2355
- }) {
2356
- const [state, dispatch] = (0, import_react6.useReducer)(reducer, initialState);
2357
- const wsClientRef = (0, import_react6.useRef)(null);
2358
- const pendingTaskResolveRef = (0, import_react6.useRef)(null);
2359
- const config = {
2360
- serverUrl,
2361
- token,
2362
- enabled,
2363
- projectRoot,
2364
- shortcut,
2365
- questionShortcut,
2366
- onTaskCreated,
2367
- onTaskProgress,
2368
- onTaskCompleted,
2369
- onTaskFailed,
2370
- onError
2371
- };
2372
- (0, import_react6.useEffect)(() => {
2373
- if (!enabled) return;
2374
- const client = new BridgeWebSocketClient(config);
2375
- wsClientRef.current = client;
2376
- const unsubConnection = client.onConnection((connected) => {
2377
- dispatch({ type: "SET_CONNECTED", connected });
2378
- });
2379
- const unsubMessage = client.onMessage((message) => {
2380
- handleMessage(message);
2381
- });
2382
- client.connect();
2383
- return () => {
2384
- unsubConnection();
2385
- unsubMessage();
2386
- client.disconnect();
2387
- wsClientRef.current = null;
2388
- };
2389
- }, [enabled, serverUrl, token]);
2390
- const handleMessage = (0, import_react6.useCallback)((message) => {
2391
- switch (message.type) {
2392
- case "task_created":
2393
- if (message.task) {
2394
- dispatch({ type: "ADD_TASK", task: message.task });
2395
- dispatch({ type: "SET_ACTIVE_TASK", task: message.task });
2396
- onTaskCreated?.(message.task.id);
2397
- pendingTaskResolveRef.current?.(message.task.id);
2398
- pendingTaskResolveRef.current = null;
2399
- }
2400
- break;
2401
- case "task_progress":
2402
- if (message.taskId && message.output) {
2403
- dispatch({
2404
- type: "UPDATE_TASK",
2405
- taskId: message.taskId,
2406
- updates: { status: "running" }
2407
- });
2408
- onTaskProgress?.(message.taskId, message.output);
2409
- }
2410
- break;
2411
- case "task_completed":
2412
- if (message.taskId) {
2413
- dispatch({
2414
- type: "UPDATE_TASK",
2415
- taskId: message.taskId,
2416
- updates: {
2417
- status: "completed",
2418
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
2419
- }
2420
- });
2421
- onTaskCompleted?.(message.taskId);
2422
- }
2423
- break;
2424
- case "task_failed":
2425
- if (message.taskId) {
2426
- dispatch({
2427
- type: "UPDATE_TASK",
2428
- taskId: message.taskId,
2429
- updates: {
2430
- status: "failed",
2431
- error: message.error,
2432
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
2433
- }
2434
- });
2435
- onTaskFailed?.(message.taskId, message.error || "Unknown error");
2436
- pendingTaskResolveRef.current?.(null);
2437
- pendingTaskResolveRef.current = null;
2438
- }
2439
- break;
2440
- case "pong":
2441
- break;
2442
- default:
2443
- console.log("[ClaudeBridge] Unknown message type:", message.type);
2444
- }
2445
- }, [onTaskCreated, onTaskProgress, onTaskCompleted, onTaskFailed]);
2446
- const matchesShortcut = (0, import_react6.useCallback)((e, shortcutStr) => {
2447
- const keys = shortcutStr.split("+");
2448
- const requiresMeta = keys.includes("Meta");
2449
- const requiresCtrl = keys.includes("Ctrl");
2450
- const requiresShift = keys.includes("Shift");
2451
- const requiresAlt = keys.includes("Alt");
2452
- const key = keys.find((k) => !["Meta", "Ctrl", "Shift", "Alt"].includes(k));
2453
- return (requiresMeta ? e.metaKey : true) && (requiresCtrl ? e.ctrlKey : true) && (requiresShift ? e.shiftKey : true) && (requiresAlt ? e.altKey : true) && e.key.toUpperCase() === key?.toUpperCase();
2454
- }, []);
2455
- (0, import_react6.useEffect)(() => {
2456
- if (!enabled) return;
2457
- const handleKeyDown = (e) => {
2458
- if (matchesShortcut(e, shortcut)) {
2459
- e.preventDefault();
2460
- if (!state.selectionMode) {
2461
- dispatch({ type: "SET_ACTIVE_TASK", task: null });
2462
- }
2463
- dispatch({ type: "SET_SELECTION_MODE", enabled: !state.selectionMode });
2464
- return;
2465
- }
2466
- if (matchesShortcut(e, questionShortcut)) {
2467
- e.preventDefault();
2468
- if (!state.questionMode) {
2469
- dispatch({ type: "SET_ACTIVE_TASK", task: null });
2470
- }
2471
- dispatch({ type: "SET_QUESTION_MODE", enabled: !state.questionMode });
2472
- return;
2473
- }
2474
- if (e.key === "Escape") {
2475
- if (state.selectionMode) {
2476
- dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2477
- }
2478
- if (state.questionMode) {
2479
- dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2480
- }
2481
- }
2482
- };
2483
- window.addEventListener("keydown", handleKeyDown);
2484
- return () => window.removeEventListener("keydown", handleKeyDown);
2485
- }, [enabled, shortcut, questionShortcut, state.selectionMode, state.questionMode, matchesShortcut]);
2486
- const enableSelectionMode = (0, import_react6.useCallback)(() => {
2487
- dispatch({ type: "SET_ACTIVE_TASK", task: null });
2488
- dispatch({ type: "SET_SELECTION_MODE", enabled: true });
2489
- }, []);
2490
- const disableSelectionMode = (0, import_react6.useCallback)(() => {
2491
- dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2492
- }, []);
2493
- const enableQuestionMode = (0, import_react6.useCallback)(() => {
2494
- dispatch({ type: "SET_ACTIVE_TASK", task: null });
2495
- dispatch({ type: "SET_QUESTION_MODE", enabled: true });
2496
- }, []);
2497
- const disableQuestionMode = (0, import_react6.useCallback)(() => {
2498
- dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2499
- }, []);
2500
- const submitTask = (0, import_react6.useCallback)(async (prompt) => {
2501
- if (!state.selectedElement || !wsClientRef.current?.isConnected) {
2502
- return null;
2503
- }
2504
- const context = captureContext(state.selectedElement);
2505
- console.log("[ClaudeBridge] submitTask captured context:", {
2506
- sourceFile: context.sourceFile,
2507
- sourceLine: context.sourceLine,
2508
- componentName: context.componentName,
2509
- element: state.selectedElement.tagName
2510
- });
2511
- const screenshot = await captureElementScreenshot(state.selectedElement);
2512
- const fullContext = {
2513
- ...context,
2514
- screenshot
2515
- };
2516
- const taskIdPromise = new Promise((resolve) => {
2517
- pendingTaskResolveRef.current = resolve;
2518
- setTimeout(() => {
2519
- if (pendingTaskResolveRef.current === resolve) {
2520
- pendingTaskResolveRef.current = null;
2521
- resolve(null);
2522
- }
2523
- }, 1e4);
2524
- });
2525
- const sent = wsClientRef.current.createTask(prompt, fullContext);
2526
- if (!sent) {
2527
- pendingTaskResolveRef.current = null;
2528
- return null;
2529
- }
2530
- dispatch({ type: "SET_SELECTION_MODE", enabled: false });
2531
- return taskIdPromise;
2532
- }, [state.selectedElement]);
2533
- const continueTask = (0, import_react6.useCallback)(async (taskId, prompt) => {
2534
- if (!wsClientRef.current?.isConnected) {
2535
- throw new Error("Not connected");
2536
- }
2537
- let context = void 0;
2538
- if (state.selectedElement) {
2539
- const baseContext = captureContext(state.selectedElement);
2540
- const screenshot = await captureElementScreenshot(state.selectedElement);
2541
- context = { ...baseContext, screenshot };
2542
- }
2543
- const sent = wsClientRef.current.continueTask(taskId, prompt, context);
2544
- if (!sent) {
2545
- throw new Error("Failed to send continue task message");
2546
- }
2547
- }, [state.selectedElement]);
2548
- const submitQuestion = (0, import_react6.useCallback)(async (prompt, includeSelectedElement = false) => {
2549
- if (!wsClientRef.current?.isConnected) {
2550
- return null;
2551
- }
2552
- let context;
2553
- if (includeSelectedElement && state.selectedElement) {
2554
- const baseContext = captureContext(state.selectedElement);
2555
- const screenshot = await captureElementScreenshot(state.selectedElement);
2556
- context = { ...baseContext, screenshot };
2557
- } else {
2558
- context = capturePageContext();
2559
- }
2560
- const questionPrompt = `[QUESTION - No code changes expected, just provide information]
2561
-
2562
- ${prompt}`;
2563
- const taskIdPromise = new Promise((resolve) => {
2564
- pendingTaskResolveRef.current = resolve;
2565
- setTimeout(() => {
2566
- if (pendingTaskResolveRef.current === resolve) {
2567
- pendingTaskResolveRef.current = null;
2568
- resolve(null);
2569
- }
2570
- }, 1e4);
2571
- });
2572
- const sent = wsClientRef.current.createTask(questionPrompt, context);
2573
- if (!sent) {
2574
- pendingTaskResolveRef.current = null;
2575
- return null;
2576
- }
2577
- dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2578
- return taskIdPromise;
2579
- }, [state.selectedElement]);
2580
- const handleElementSelect = (0, import_react6.useCallback)((element) => {
2581
- dispatch({ type: "SET_SELECTED_ELEMENT", element });
2582
- }, []);
2583
- const handlePromptSubmit = (0, import_react6.useCallback)(async (prompt) => {
2584
- await submitTask(prompt);
2585
- }, [submitTask]);
2586
- const handlePromptClose = (0, import_react6.useCallback)(() => {
2587
- dispatch({ type: "SET_SELECTED_ELEMENT", element: null });
2588
- }, []);
2589
- const handleQuestionSubmit = (0, import_react6.useCallback)(async (prompt, includeElement) => {
2590
- await submitQuestion(prompt, includeElement);
2591
- }, [submitQuestion]);
2592
- const handleQuestionClose = (0, import_react6.useCallback)(() => {
2593
- dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2594
- }, []);
2595
- const handleSwitchToQuestionMode = (0, import_react6.useCallback)(() => {
2596
- dispatch({ type: "SET_QUESTION_MODE", enabled: true });
2597
- }, []);
2598
- const handleSwitchToTaskMode = (0, import_react6.useCallback)(() => {
2599
- dispatch({ type: "SET_QUESTION_MODE", enabled: false });
2600
- if (!state.selectedElement) {
2601
- dispatch({ type: "SET_SELECTION_MODE", enabled: true });
2602
- }
2603
- }, [state.selectedElement]);
2604
- const handleToastDismiss = (0, import_react6.useCallback)(() => {
2605
- dispatch({ type: "SET_ACTIVE_TASK", task: null });
2606
- }, []);
2607
- const contextValue = {
2608
- state,
2609
- config,
2610
- enableSelectionMode,
2611
- disableSelectionMode,
2612
- enableQuestionMode,
2613
- disableQuestionMode,
2614
- submitTask,
2615
- submitQuestion,
2616
- continueTask
2617
- };
2618
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ClaudeBridgeContext.Provider, { value: contextValue, children: [
2619
- children,
2620
- enabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2621
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2622
- SelectionOverlay,
2623
- {
2624
- active: state.selectionMode,
2625
- onElementSelect: handleElementSelect,
2626
- selectedElement: state.selectedElement
2627
- }
2628
- ),
2629
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2630
- PromptDialog,
2631
- {
2632
- open: state.selectedElement !== null && !state.questionMode,
2633
- element: state.selectedElement,
2634
- onSubmit: handlePromptSubmit,
2635
- onClose: handlePromptClose,
2636
- onSwitchToQuestionMode: handleSwitchToQuestionMode,
2637
- isSubmitting: state.activeTask?.status === "starting" || state.activeTask?.status === "running"
2638
- }
2639
- ),
2640
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2641
- QuestionDialog,
2642
- {
2643
- open: state.questionMode,
2644
- selectedElement: state.selectedElement,
2645
- onSubmit: handleQuestionSubmit,
2646
- onClose: handleQuestionClose,
2647
- onSwitchToTaskMode: handleSwitchToTaskMode,
2648
- isSubmitting: state.activeTask?.status === "starting" || state.activeTask?.status === "running"
2649
- }
2650
- ),
2651
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2652
- TaskStatusToast,
2653
- {
2654
- task: state.activeTask,
2655
- serverUrl,
2656
- token,
2657
- onDismiss: handleToastDismiss
2658
- }
2659
- )
2660
- ] })
2661
- ] });
2662
- }
2663
- function useClaudeBridge() {
2664
- const context = (0, import_react6.useContext)(ClaudeBridgeContext);
2665
- if (!context) {
2666
- throw new Error("useClaudeBridge must be used within a ClaudeBridgeProvider");
2667
- }
2668
- return context;
2669
- }
2670
3371
  // Annotate the CommonJS export names for ESM import in node:
2671
3372
  0 && (module.exports = {
2672
3373
  ClaudeBridgeProvider,
2673
3374
  ClaudeTerminal,
3375
+ SessionPanel,
3376
+ SessionTabs,
2674
3377
  TaskStatusToast,
3378
+ VoiceInput,
2675
3379
  captureContext,
2676
3380
  captureElementScreenshot,
2677
3381
  capturePageContext,
2678
- useClaudeBridge
3382
+ getSessionDisplayName,
3383
+ isSessionActive,
3384
+ useClaudeBridge,
3385
+ useSessionStorage,
3386
+ useSessions,
3387
+ useVoiceInput
2679
3388
  });
2680
3389
  //# sourceMappingURL=index.cjs.map