replicas-cli 0.2.340 → 0.2.341

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +171 -131
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -9597,7 +9597,7 @@ var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
9597
9597
  var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
9598
9598
 
9599
9599
  // ../shared/src/cli-version.ts
9600
- var CLI_VERSION = "0.2.340";
9600
+ var CLI_VERSION = "0.2.341";
9601
9601
 
9602
9602
  // ../shared/src/engine/environment.ts
9603
9603
  var DESKTOP_NOVNC_PORT = 6080;
@@ -16298,7 +16298,7 @@ import { createCliRenderer } from "@opentui/core";
16298
16298
  import { createRoot } from "@opentui/react";
16299
16299
 
16300
16300
  // src/interactive/App.tsx
16301
- import { useState as useState8, useEffect as useEffect3, useMemo as useMemo6, useCallback as useCallback8, useRef as useRef5 } from "react";
16301
+ import { useState as useState7, useEffect as useEffect2, useMemo as useMemo5, useCallback as useCallback9, useRef as useRef6 } from "react";
16302
16302
  import { useKeyboard as useKeyboard5, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
16303
16303
  import { QueryClient } from "@tanstack/react-query";
16304
16304
 
@@ -16320,8 +16320,74 @@ function useEffectOnMount(effect) {
16320
16320
  useEffect(effect, []);
16321
16321
  }
16322
16322
 
16323
+ // ../shared/src/hooks/useReconnectingSseStream.ts
16324
+ import { useCallback, useRef, useSyncExternalStore } from "react";
16325
+ function useReconnectingSseStream(options) {
16326
+ const { enabled, openStream, onEvent, onConnect, onNotOk, reconnectDelayMs = 1e3, coalesceMs } = options;
16327
+ const connectedRef = useRef(false);
16328
+ const subscribe = useCallback(
16329
+ (notify) => {
16330
+ if (!enabled) return () => {
16331
+ };
16332
+ const controller = new AbortController();
16333
+ let cancelled = false;
16334
+ let debounce = null;
16335
+ const setConnected = (value) => {
16336
+ if (connectedRef.current === value) return;
16337
+ connectedRef.current = value;
16338
+ notify();
16339
+ };
16340
+ const emit = (event) => {
16341
+ if (coalesceMs === void 0) {
16342
+ void onEvent(event);
16343
+ return;
16344
+ }
16345
+ if (debounce) clearTimeout(debounce);
16346
+ debounce = setTimeout(() => void onEvent(event), coalesceMs);
16347
+ };
16348
+ const connect = async () => {
16349
+ try {
16350
+ const response = await openStream(controller.signal);
16351
+ if (!response.ok || !response.body) {
16352
+ setConnected(false);
16353
+ return onNotOk?.(response) === "stop";
16354
+ }
16355
+ setConnected(true);
16356
+ onConnect?.();
16357
+ await readSseStream(response.body, emit, () => !cancelled);
16358
+ return false;
16359
+ } catch {
16360
+ setConnected(false);
16361
+ return false;
16362
+ }
16363
+ };
16364
+ const run = async () => {
16365
+ while (!cancelled) {
16366
+ const stop = await connect();
16367
+ if (cancelled || stop) break;
16368
+ await new Promise((resolve2) => setTimeout(resolve2, reconnectDelayMs));
16369
+ }
16370
+ };
16371
+ run().catch(() => setConnected(false));
16372
+ return () => {
16373
+ cancelled = true;
16374
+ controller.abort();
16375
+ if (debounce) clearTimeout(debounce);
16376
+ setConnected(false);
16377
+ };
16378
+ },
16379
+ [enabled, openStream, onEvent, onConnect, onNotOk, reconnectDelayMs, coalesceMs]
16380
+ );
16381
+ const connected = useSyncExternalStore(
16382
+ subscribe,
16383
+ () => connectedRef.current,
16384
+ () => false
16385
+ );
16386
+ return { connected };
16387
+ }
16388
+
16323
16389
  // ../shared/src/hooks/fetch.ts
16324
- import { useCallback } from "react";
16390
+ import { useCallback as useCallback2 } from "react";
16325
16391
  var REDIRECT_STATUSES2 = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
16326
16392
  var MAX_REDIRECTS2 = 5;
16327
16393
  async function fetchWithRedirects2(url, init, attempt = 0) {
@@ -16347,7 +16413,7 @@ async function fetchWithRedirects2(url, init, attempt = 0) {
16347
16413
  }
16348
16414
  function useOrgFetch() {
16349
16415
  const auth = useReplicasAuth();
16350
- return useCallback(
16416
+ return useCallback2(
16351
16417
  async function orgFetch(url, options) {
16352
16418
  const token = await auth.getAccessToken();
16353
16419
  const orgId = auth.getOrganizationId();
@@ -16375,7 +16441,7 @@ function useOrgFetch() {
16375
16441
  }
16376
16442
  function useRawOrgFetch() {
16377
16443
  const auth = useReplicasAuth();
16378
- return useCallback(
16444
+ return useCallback2(
16379
16445
  async function rawOrgFetch(url, options) {
16380
16446
  const token = await auth.getAccessToken();
16381
16447
  const orgId = auth.getOrganizationId();
@@ -16392,7 +16458,7 @@ function useRawOrgFetch() {
16392
16458
  }
16393
16459
 
16394
16460
  // ../shared/src/hooks/useWorkspaces.ts
16395
- import { useCallback as useCallback2 } from "react";
16461
+ import { useCallback as useCallback3 } from "react";
16396
16462
  import { useQuery, useMutation } from "@tanstack/react-query";
16397
16463
  function useWorkspaces(viewModes, page = 1, limit = 100) {
16398
16464
  const auth = useReplicasAuth();
@@ -16444,7 +16510,7 @@ function useWaitForWorkspaceActive() {
16444
16510
  const auth = useReplicasAuth();
16445
16511
  const orgFetch = useOrgFetch();
16446
16512
  const orgId = auth.getOrganizationId();
16447
- return useCallback2(async (workspaceId) => {
16513
+ return useCallback3(async (workspaceId) => {
16448
16514
  try {
16449
16515
  const result = await auth.queryClient.fetchQuery({
16450
16516
  queryKey: ["workspace-wait-active", orgId, workspaceId],
@@ -16516,7 +16582,7 @@ function useGenerateWorkspaceName() {
16516
16582
  }
16517
16583
 
16518
16584
  // ../shared/src/hooks/useWorkspaceEngine.ts
16519
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useState } from "react";
16585
+ import { useCallback as useCallback4 } from "react";
16520
16586
  import { useQuery as useQuery2, useMutation as useMutation2 } from "@tanstack/react-query";
16521
16587
  function upsertChat(chats, chat) {
16522
16588
  const existingIndex = chats.findIndex((item) => item.id === chat.id);
@@ -16644,8 +16710,7 @@ function useWorkspaceEvents(workspaceId, enabled = true) {
16644
16710
  const auth = useReplicasAuth();
16645
16711
  const rawFetch = useRawOrgFetch();
16646
16712
  const qc = auth.queryClient;
16647
- const [connected, setConnected] = useState(false);
16648
- const handleEvent = useCallback3((event) => {
16713
+ const handleEvent = useCallback4((event) => {
16649
16714
  if (!workspaceId) return;
16650
16715
  const chatsKey = ["workspace-chats", workspaceId];
16651
16716
  if (event.type === "chat.created" || event.type === "chat.updated") {
@@ -16723,43 +16788,18 @@ function useWorkspaceEvents(workspaceId, enabled = true) {
16723
16788
  return;
16724
16789
  }
16725
16790
  }, [qc, workspaceId]);
16726
- useEffect2(() => {
16727
- if (!workspaceId || !enabled) return;
16728
- const controller = new AbortController();
16729
- let cancelled = false;
16730
- const connect = async () => {
16731
- try {
16732
- const response = await rawFetch(`/v1/workspaces/${workspaceId}/events`, {
16733
- headers: { "Accept": "text/event-stream" },
16734
- signal: controller.signal
16735
- });
16736
- if (!response.ok || !response.body) {
16737
- setConnected(false);
16738
- return false;
16739
- }
16740
- setConnected(true);
16741
- await readSseStream(response.body, handleEvent, () => !cancelled);
16742
- return false;
16743
- } catch {
16744
- setConnected(false);
16745
- return false;
16746
- }
16747
- };
16748
- const run = async () => {
16749
- while (!cancelled) {
16750
- await connect();
16751
- if (cancelled) break;
16752
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
16753
- }
16754
- };
16755
- run().catch(() => setConnected(false));
16756
- return () => {
16757
- cancelled = true;
16758
- controller.abort();
16759
- setConnected(false);
16760
- };
16761
- }, [enabled, handleEvent, rawFetch, workspaceId]);
16762
- return useMemo(() => ({ connected }), [connected]);
16791
+ const openStream = useCallback4(
16792
+ (signal) => rawFetch(`/v1/workspaces/${workspaceId}/events`, {
16793
+ headers: { Accept: "text/event-stream" },
16794
+ signal
16795
+ }),
16796
+ [rawFetch, workspaceId]
16797
+ );
16798
+ return useReconnectingSseStream({
16799
+ enabled: workspaceId != null && enabled,
16800
+ openStream,
16801
+ onEvent: handleEvent
16802
+ });
16763
16803
  }
16764
16804
 
16765
16805
  // ../shared/src/hooks/useRepositories.ts
@@ -16839,7 +16879,7 @@ function StatusBar({ focusPanel, viewingDiff, hasDiffAvailable }) {
16839
16879
  }
16840
16880
 
16841
16881
  // src/interactive/components/WorkspaceSidebar.tsx
16842
- import { useState as useState2, useMemo as useMemo2, useCallback as useCallback4 } from "react";
16882
+ import { useState, useMemo, useCallback as useCallback5 } from "react";
16843
16883
  import { useKeyboard, useTerminalDimensions } from "@opentui/react";
16844
16884
  import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
16845
16885
  function flattenGroups(groups, collapsedGroups) {
@@ -16885,17 +16925,17 @@ function WorkspaceSidebar({
16885
16925
  loading
16886
16926
  }) {
16887
16927
  const { height: termHeight } = useTerminalDimensions();
16888
- const [collapsedGroups, setCollapsedGroups] = useState2(/* @__PURE__ */ new Set());
16889
- const [cursorIndex, setCursorIndex] = useState2(0);
16890
- const [scrollOffset, setScrollOffset] = useState2(0);
16891
- const [confirmArchive, setConfirmArchive] = useState2(null);
16892
- const items = useMemo2(() => flattenGroups(groups, collapsedGroups), [groups, collapsedGroups]);
16928
+ const [collapsedGroups, setCollapsedGroups] = useState(/* @__PURE__ */ new Set());
16929
+ const [cursorIndex, setCursorIndex] = useState(0);
16930
+ const [scrollOffset, setScrollOffset] = useState(0);
16931
+ const [confirmArchive, setConfirmArchive] = useState(null);
16932
+ const items = useMemo(() => flattenGroups(groups, collapsedGroups), [groups, collapsedGroups]);
16893
16933
  const clampedCursor = Math.min(cursorIndex, Math.max(0, items.length - 1));
16894
16934
  if (clampedCursor !== cursorIndex) {
16895
16935
  setCursorIndex(clampedCursor);
16896
16936
  }
16897
16937
  const visibleHeight = Math.max(1, termHeight - 4);
16898
- const adjustScroll = useCallback4(
16938
+ const adjustScroll = useCallback5(
16899
16939
  (newCursor) => {
16900
16940
  let newOffset = scrollOffset;
16901
16941
  if (newCursor < newOffset) {
@@ -16907,7 +16947,7 @@ function WorkspaceSidebar({
16907
16947
  },
16908
16948
  [scrollOffset, visibleHeight]
16909
16949
  );
16910
- const moveCursor = useCallback4(
16950
+ const moveCursor = useCallback5(
16911
16951
  (next) => {
16912
16952
  const len = items.length;
16913
16953
  if (len === 0) return;
@@ -16917,7 +16957,7 @@ function WorkspaceSidebar({
16917
16957
  },
16918
16958
  [items.length, adjustScroll]
16919
16959
  );
16920
- const handleAction = useCallback4(
16960
+ const handleAction = useCallback5(
16921
16961
  (item) => {
16922
16962
  if (item.type === "group") {
16923
16963
  setCollapsedGroups((prev) => {
@@ -16937,7 +16977,7 @@ function WorkspaceSidebar({
16937
16977
  },
16938
16978
  [onSelectWorkspace, onCreateWorkspace]
16939
16979
  );
16940
- const handleArchive = useCallback4(() => {
16980
+ const handleArchive = useCallback5(() => {
16941
16981
  const item = items[cursorIndex];
16942
16982
  if (!item || item.type !== "workspace") return;
16943
16983
  if (confirmArchive === item.workspaceId) {
@@ -16947,7 +16987,7 @@ function WorkspaceSidebar({
16947
16987
  setConfirmArchive(item.workspaceId);
16948
16988
  }
16949
16989
  }, [items, cursorIndex, confirmArchive, onArchiveWorkspace]);
16950
- const moveCursorAndClearConfirm = useCallback4(
16990
+ const moveCursorAndClearConfirm = useCallback5(
16951
16991
  (next) => {
16952
16992
  setConfirmArchive(null);
16953
16993
  moveCursor(next);
@@ -17015,7 +17055,7 @@ function WorkspaceSidebar({
17015
17055
  return;
17016
17056
  }
17017
17057
  });
17018
- const handleItemClick = useCallback4(
17058
+ const handleItemClick = useCallback5(
17019
17059
  (globalIndex) => {
17020
17060
  setConfirmArchive(null);
17021
17061
  setCursorIndex(globalIndex);
@@ -17024,7 +17064,7 @@ function WorkspaceSidebar({
17024
17064
  },
17025
17065
  [items, handleAction]
17026
17066
  );
17027
- const handleArchiveClick = useCallback4(
17067
+ const handleArchiveClick = useCallback5(
17028
17068
  (workspaceId, e) => {
17029
17069
  e?.preventDefault?.();
17030
17070
  if (confirmArchive === workspaceId) {
@@ -17162,11 +17202,11 @@ function WorkspaceSidebar({
17162
17202
  }
17163
17203
 
17164
17204
  // src/interactive/components/ChatArea.tsx
17165
- import { useRef as useRef2 } from "react";
17205
+ import { useRef as useRef3 } from "react";
17166
17206
  import { useKeyboard as useKeyboard3 } from "@opentui/react";
17167
17207
 
17168
17208
  // src/interactive/components/ChatMessage.tsx
17169
- import { useState as useState5, useMemo as useMemo4 } from "react";
17209
+ import { useState as useState4, useMemo as useMemo3 } from "react";
17170
17210
 
17171
17211
  // src/interactive/components/Spinner.tsx
17172
17212
  import "opentui-spinner/react";
@@ -17181,10 +17221,10 @@ function SpinnerLabel({ color, label }) {
17181
17221
  }
17182
17222
 
17183
17223
  // src/interactive/components/StructuredUserMessage.tsx
17184
- import { useState as useState4 } from "react";
17224
+ import { useState as useState3 } from "react";
17185
17225
 
17186
17226
  // src/interactive/components/diff-utils.tsx
17187
- import React2, { useState as useState3, useMemo as useMemo3, useCallback as useCallback5, useRef } from "react";
17227
+ import React2, { useState as useState2, useMemo as useMemo2, useCallback as useCallback6, useRef as useRef2 } from "react";
17188
17228
  import { SyntaxStyle } from "@opentui/core";
17189
17229
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
17190
17230
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
@@ -17314,7 +17354,7 @@ function diffProps(filePath) {
17314
17354
  };
17315
17355
  }
17316
17356
  function DiffView({ diff, filePath }) {
17317
- const hunks = useMemo3(() => splitDiffByHunks(diff), [diff]);
17357
+ const hunks = useMemo2(() => splitDiffByHunks(diff), [diff]);
17318
17358
  const props = diffProps(filePath);
17319
17359
  if (hunks.length <= 1) {
17320
17360
  return /* @__PURE__ */ jsx4("diff", { diff: ensureUnifiedHeaders(diff, filePath), ...props });
@@ -17427,16 +17467,16 @@ function buildFileTree(files) {
17427
17467
  }
17428
17468
  var FILE_PANEL_WIDTH = 32;
17429
17469
  function DiffViewer({ diff, repoName, focused }) {
17430
- const files = useMemo3(() => splitDiffByFile(diff), [diff]);
17431
- const tree = useMemo3(() => buildFileTree(files), [files]);
17432
- const [selectedFileIndex, setSelectedFileIndex] = useState3(0);
17433
- const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
17434
- const [cursorIndex, setCursorIndex] = useState3(0);
17435
- const [innerFocus, setInnerFocus] = useState3("files");
17470
+ const files = useMemo2(() => splitDiffByFile(diff), [diff]);
17471
+ const tree = useMemo2(() => buildFileTree(files), [files]);
17472
+ const [selectedFileIndex, setSelectedFileIndex] = useState2(0);
17473
+ const [collapsedFolders, setCollapsedFolders] = useState2(/* @__PURE__ */ new Set());
17474
+ const [cursorIndex, setCursorIndex] = useState2(0);
17475
+ const [innerFocus, setInnerFocus] = useState2("files");
17436
17476
  const safeFileIndex = Math.min(selectedFileIndex, Math.max(0, files.length - 1));
17437
- const diffScrollRef = useRef(null);
17438
- const fileTreeScrollRef = useRef(null);
17439
- const visibleTree = useMemo3(() => {
17477
+ const diffScrollRef = useRef2(null);
17478
+ const fileTreeScrollRef = useRef2(null);
17479
+ const visibleTree = useMemo2(() => {
17440
17480
  const result = [];
17441
17481
  let hideUntilDepth = null;
17442
17482
  for (const node of tree) {
@@ -17452,12 +17492,12 @@ function DiffViewer({ diff, repoName, focused }) {
17452
17492
  return result;
17453
17493
  }, [tree, collapsedFolders]);
17454
17494
  const safeCursorIndex = Math.min(cursorIndex, Math.max(0, visibleTree.length - 1));
17455
- const scrollCursorIntoView = useCallback5((treeIdx) => {
17495
+ const scrollCursorIntoView = useCallback6((treeIdx) => {
17456
17496
  const node = visibleTree[treeIdx];
17457
17497
  if (!node) return;
17458
17498
  fileTreeScrollRef.current?.scrollChildIntoView(`tree-${node.path}`);
17459
17499
  }, [visibleTree]);
17460
- const moveCursor = useCallback5((next) => {
17500
+ const moveCursor = useCallback6((next) => {
17461
17501
  const len = visibleTree.length;
17462
17502
  if (len === 0) return;
17463
17503
  const wrapped = (next % len + len) % len;
@@ -17468,7 +17508,7 @@ function DiffViewer({ diff, repoName, focused }) {
17468
17508
  setSelectedFileIndex(node.fileIndex);
17469
17509
  }
17470
17510
  }, [visibleTree, scrollCursorIntoView]);
17471
- const toggleFolder = useCallback5((folderPath) => {
17511
+ const toggleFolder = useCallback6((folderPath) => {
17472
17512
  setCollapsedFolders((prev) => {
17473
17513
  const next = new Set(prev);
17474
17514
  if (next.has(folderPath)) next.delete(folderPath);
@@ -17476,7 +17516,7 @@ function DiffViewer({ diff, repoName, focused }) {
17476
17516
  return next;
17477
17517
  });
17478
17518
  }, []);
17479
- const selectFile = useCallback5((fileIndex, treeIndex) => {
17519
+ const selectFile = useCallback6((fileIndex, treeIndex) => {
17480
17520
  setSelectedFileIndex(fileIndex);
17481
17521
  setCursorIndex(treeIndex);
17482
17522
  scrollCursorIntoView(treeIndex);
@@ -17513,7 +17553,7 @@ function DiffViewer({ diff, repoName, focused }) {
17513
17553
  }
17514
17554
  }
17515
17555
  });
17516
- const statsColumnWidth = useMemo3(() => {
17556
+ const statsColumnWidth = useMemo2(() => {
17517
17557
  let max = 0;
17518
17558
  for (const f of files) {
17519
17559
  const a = f.added > 0 ? `+${f.added}`.length : 0;
@@ -17697,7 +17737,7 @@ function MessageBody({ children }) {
17697
17737
  return /* @__PURE__ */ jsx5("text", { fg: "#ffffff", selectable: true, children });
17698
17738
  }
17699
17739
  function ExpandableSection({ label, children }) {
17700
- const [expanded, setExpanded] = useState4(false);
17740
+ const [expanded, setExpanded] = useState3(false);
17701
17741
  if (!children) return null;
17702
17742
  return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
17703
17743
  /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
@@ -17711,7 +17751,7 @@ function ExpandableSection({ label, children }) {
17711
17751
  ] });
17712
17752
  }
17713
17753
  function ExpandableDiffSection({ label, diffContent, filePath }) {
17714
- const [expanded, setExpanded] = useState4(false);
17754
+ const [expanded, setExpanded] = useState3(false);
17715
17755
  if (!diffContent) return null;
17716
17756
  return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
17717
17757
  /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
@@ -17954,7 +17994,7 @@ function ExpandableAction({
17954
17994
  status,
17955
17995
  children
17956
17996
  }) {
17957
- const [expanded, setExpanded] = useState5(false);
17997
+ const [expanded, setExpanded] = useState4(false);
17958
17998
  return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
17959
17999
  /* @__PURE__ */ jsx6(
17960
18000
  ActionLine,
@@ -17979,9 +18019,9 @@ function getChangeStyle(action) {
17979
18019
  return CHANGE_STYLE[action] ?? DEFAULT_CHANGE_STYLE;
17980
18020
  }
17981
18021
  function PatchOperation({ op, defaultExpanded = false }) {
17982
- const [expanded, setExpanded] = useState5(defaultExpanded);
18022
+ const [expanded, setExpanded] = useState4(defaultExpanded);
17983
18023
  const { color, sign } = getChangeStyle(op.action);
17984
- const stats = useMemo4(() => op.diff ? countDiffStats(op.diff) : null, [op.diff]);
18024
+ const stats = useMemo3(() => op.diff ? countDiffStats(op.diff) : null, [op.diff]);
17985
18025
  if (!op.diff) {
17986
18026
  return /* @__PURE__ */ jsxs6("text", { children: [
17987
18027
  /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
@@ -18021,7 +18061,7 @@ function isStructuredPrompt(p) {
18021
18061
  return p.source !== "raw";
18022
18062
  }
18023
18063
  function UserMessageContent({ content }) {
18024
- const parsed = useMemo4(() => parseUserMessage(content), [content]);
18064
+ const parsed = useMemo3(() => parseUserMessage(content), [content]);
18025
18065
  return /* @__PURE__ */ jsx6(
18026
18066
  "box",
18027
18067
  {
@@ -18158,10 +18198,10 @@ function ChatArea({
18158
18198
  agentLabel,
18159
18199
  agentConnectHelp
18160
18200
  }) {
18161
- const textareaRef = useRef2(null);
18162
- const scrollboxRef = useRef2(null);
18163
- const isAtBottomRef = useRef2(true);
18164
- const onSendMessageRef = useRef2(onSendMessage);
18201
+ const textareaRef = useRef3(null);
18202
+ const scrollboxRef = useRef3(null);
18203
+ const isAtBottomRef = useRef3(true);
18204
+ const onSendMessageRef = useRef3(onSendMessage);
18165
18205
  onSendMessageRef.current = onSendMessage;
18166
18206
  const inputFocused = focusPanel === "chat-input";
18167
18207
  const tabsFocused = focusPanel === "chat-tabs";
@@ -18333,7 +18373,7 @@ function ChatArea({
18333
18373
  }
18334
18374
 
18335
18375
  // src/interactive/components/WorkspaceInfo.tsx
18336
- import React6, { useState as useState6, useMemo as useMemo5, useCallback as useCallback6, useRef as useRef3 } from "react";
18376
+ import React6, { useState as useState5, useMemo as useMemo4, useCallback as useCallback7, useRef as useRef4 } from "react";
18337
18377
  import open4 from "open";
18338
18378
  import { useKeyboard as useKeyboard4 } from "@opentui/react";
18339
18379
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
@@ -18521,19 +18561,19 @@ function ViewModeRow({
18521
18561
  }
18522
18562
  function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, agentAvailability, previews, onWakeWorkspace, onViewDiff, wakingWorkspaceId, viewMode, viewingDiffRepoName, onSelectChatMode, onSelectDiffMode, onCreatePr, isPlanMode }) {
18523
18563
  const borderColor = focused ? "#3eeba3" : "#333333";
18524
- const [cursorIndex, setCursorIndex] = useState6(0);
18525
- const interactiveItems = useMemo5(() => {
18564
+ const [cursorIndex, setCursorIndex] = useState5(0);
18565
+ const interactiveItems = useMemo4(() => {
18526
18566
  if (!status || !workspaceName) return [];
18527
18567
  return buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId);
18528
18568
  }, [workspaceId, workspaceName, status, previews, wakingWorkspaceId]);
18529
- const diffItems = useMemo5(
18569
+ const diffItems = useMemo4(
18530
18570
  () => interactiveItems.filter((item) => item.type === "diff"),
18531
18571
  [interactiveItems]
18532
18572
  );
18533
18573
  const hasAnyDiff = diffItems.length > 0;
18534
18574
  const safeCursor = interactiveItems.length > 0 ? Math.min(cursorIndex, interactiveItems.length - 1) : 0;
18535
- const scrollboxRef = useRef3(null);
18536
- const moveCursor = useCallback6(
18575
+ const scrollboxRef = useRef4(null);
18576
+ const moveCursor = useCallback7(
18537
18577
  (next) => {
18538
18578
  const len = interactiveItems.length;
18539
18579
  if (len === 0) return;
@@ -18544,7 +18584,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, a
18544
18584
  },
18545
18585
  [interactiveItems]
18546
18586
  );
18547
- const handleAction = useCallback6(
18587
+ const handleAction = useCallback7(
18548
18588
  (item) => {
18549
18589
  if (!item) return;
18550
18590
  switch (item.type) {
@@ -18613,7 +18653,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, a
18613
18653
  return;
18614
18654
  }
18615
18655
  });
18616
- const isHighlighted = useCallback6(
18656
+ const isHighlighted = useCallback7(
18617
18657
  (item) => {
18618
18658
  if (!focused) return false;
18619
18659
  const idx = interactiveItems.indexOf(item);
@@ -18621,7 +18661,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, a
18621
18661
  },
18622
18662
  [focused, interactiveItems, safeCursor]
18623
18663
  );
18624
- const findItem = useCallback6(
18664
+ const findItem = useCallback7(
18625
18665
  (type, key) => {
18626
18666
  return interactiveItems.find((item) => {
18627
18667
  if (item.type !== type) return false;
@@ -18915,7 +18955,7 @@ function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, a
18915
18955
  import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
18916
18956
 
18917
18957
  // src/interactive/toast-context.tsx
18918
- import { createContext as createContext2, useContext as useContext2, useState as useState7, useCallback as useCallback7, useRef as useRef4 } from "react";
18958
+ import { createContext as createContext2, useContext as useContext2, useState as useState6, useCallback as useCallback8, useRef as useRef5 } from "react";
18919
18959
  import { jsx as jsx9 } from "@opentui/react/jsx-runtime";
18920
18960
  var ToastContext = createContext2(null);
18921
18961
  function useToast() {
@@ -18924,9 +18964,9 @@ function useToast() {
18924
18964
  return ctx;
18925
18965
  }
18926
18966
  function ToastProvider({ children }) {
18927
- const [current, setCurrent] = useState7(null);
18928
- const timeoutRef = useRef4(null);
18929
- const show = useCallback7((options) => {
18967
+ const [current, setCurrent] = useState6(null);
18968
+ const timeoutRef = useRef5(null);
18969
+ const show = useCallback8((options) => {
18930
18970
  const { message, variant = "info", duration = 3e3 } = options;
18931
18971
  setCurrent({ message, variant });
18932
18972
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -18934,7 +18974,7 @@ function ToastProvider({ children }) {
18934
18974
  setCurrent(null);
18935
18975
  }, duration);
18936
18976
  }, []);
18937
- const error = useCallback7((err) => {
18977
+ const error = useCallback8((err) => {
18938
18978
  const message = err instanceof Error ? err.message : "An error occurred";
18939
18979
  show({ message, variant: "error", duration: 5e3 });
18940
18980
  }, [show]);
@@ -19013,7 +19053,7 @@ function AppInner() {
19013
19053
  const renderer = useRenderer();
19014
19054
  const { width } = useTerminalDimensions3();
19015
19055
  const toast = useToast();
19016
- const rendererRef = useRef5(renderer);
19056
+ const rendererRef = useRef6(renderer);
19017
19057
  rendererRef.current = renderer;
19018
19058
  if (!workspacePollingStarted) {
19019
19059
  workspacePollingStarted = true;
@@ -19022,7 +19062,7 @@ function AppInner() {
19022
19062
  rendererRef.current.requestRender();
19023
19063
  }, 3e4);
19024
19064
  }
19025
- useEffect3(() => {
19065
+ useEffect2(() => {
19026
19066
  const handler = (selection) => {
19027
19067
  const text = selection.getSelectedText();
19028
19068
  if (text) {
@@ -19035,15 +19075,15 @@ function AppInner() {
19035
19075
  renderer.off("selection", handler);
19036
19076
  };
19037
19077
  }, [renderer, toast]);
19038
- const [selectedWorkspaceId, setSelectedWorkspaceId] = useState8(null);
19039
- const [selectedChatId, setSelectedChatId] = useState8(null);
19040
- const [focusPanel, setFocusPanel] = useState8("sidebar");
19041
- const [taskMode, setTaskMode] = useState8("build");
19042
- const [mockMessages, setMockMessages] = useState8([]);
19043
- const [mockThinking, setMockThinking] = useState8(false);
19044
- const pendingMessageRef = useRef5(/* @__PURE__ */ new Map());
19045
- const [viewingDiff, setViewingDiff] = useState8(null);
19046
- const [lastViewedDiff, setLastViewedDiff] = useState8(null);
19078
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState7(null);
19079
+ const [selectedChatId, setSelectedChatId] = useState7(null);
19080
+ const [focusPanel, setFocusPanel] = useState7("sidebar");
19081
+ const [taskMode, setTaskMode] = useState7("build");
19082
+ const [mockMessages, setMockMessages] = useState7([]);
19083
+ const [mockThinking, setMockThinking] = useState7(false);
19084
+ const pendingMessageRef = useRef6(/* @__PURE__ */ new Map());
19085
+ const [viewingDiff, setViewingDiff] = useState7(null);
19086
+ const [lastViewedDiff, setLastViewedDiff] = useState7(null);
19047
19087
  const { data: workspacesData, isLoading: loadingWorkspaces } = useWorkspaces(WORKSPACE_SIDEBAR_VIEWS);
19048
19088
  const { data: setsData } = useRepositorySets();
19049
19089
  const { data: envsData } = useEnvironments();
@@ -19059,7 +19099,7 @@ function AppInner() {
19059
19099
  );
19060
19100
  const { data: chatsData } = useWorkspaceChats(isMockSelected ? null : selectedWorkspaceId);
19061
19101
  const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
19062
- const resolvedChatId = useMemo6(() => {
19102
+ const resolvedChatId = useMemo5(() => {
19063
19103
  if (selectedChatId && chats.find((c) => c.id === selectedChatId)) {
19064
19104
  return selectedChatId;
19065
19105
  }
@@ -19078,13 +19118,13 @@ function AppInner() {
19078
19118
  const archiveWorkspaceMutation = useArchiveWorkspace();
19079
19119
  const wakeWorkspaceMutation = useWakeWorkspace();
19080
19120
  const generateNameMutation = useGenerateWorkspaceName();
19081
- const [wakingWorkspaceId, setWakingWorkspaceId] = useState8(null);
19121
+ const [wakingWorkspaceId, setWakingWorkspaceId] = useState7(null);
19082
19122
  const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, resolvedChatId);
19083
19123
  const interruptMutation = useInterruptChat(selectedWorkspaceId, resolvedChatId);
19084
19124
  const repositorySets = setsData?.repository_sets ?? [];
19085
19125
  const environments = envsData?.environments ?? [];
19086
19126
  const previews = previewsData?.previews ?? [];
19087
- const groups = useMemo6(
19127
+ const groups = useMemo5(
19088
19128
  () => buildGroups(workspaces, workspacesData, environments, repositorySets),
19089
19129
  [workspaces, workspacesData, environments, repositorySets]
19090
19130
  );
@@ -19094,7 +19134,7 @@ function AppInner() {
19094
19134
  const agentAvailable = agentAvailability ? selectedAgent === "codex" ? agentAvailability.codex.available : selectedAgent === "cursor" ? agentAvailability.cursor.available : selectedAgent === "relay" ? agentAvailability.relay.available : agentAvailability.claude.available : true;
19095
19135
  const agentLabel = getProviderDisplayName(selectedAgent, "long");
19096
19136
  const agentConnectHelp = getAgentConnectHelp(selectedAgent);
19097
- const displayMessages = useMemo6(() => {
19137
+ const displayMessages = useMemo5(() => {
19098
19138
  if (isMockSelected) return mockMessages;
19099
19139
  const rawEvents = historyData?.events ?? [];
19100
19140
  const events = rawEvents.filter(isAgentBackendEvent);
@@ -19103,7 +19143,7 @@ function AppInner() {
19103
19143
  }, [isMockSelected, mockMessages, historyData, selectedChat?.provider]);
19104
19144
  const showInfoPanel = width >= 100;
19105
19145
  const showWorkspacePanel = width >= 60;
19106
- const [prevIsMockSelected, setPrevIsMockSelected] = useState8(isMockSelected);
19146
+ const [prevIsMockSelected, setPrevIsMockSelected] = useState7(isMockSelected);
19107
19147
  if (prevIsMockSelected !== isMockSelected) {
19108
19148
  setPrevIsMockSelected(isMockSelected);
19109
19149
  if (prevIsMockSelected && !isMockSelected) {
@@ -19112,7 +19152,7 @@ function AppInner() {
19112
19152
  setMockThinking(false);
19113
19153
  }
19114
19154
  }
19115
- useEffect3(() => {
19155
+ useEffect2(() => {
19116
19156
  if (!selectedWorkspaceId || isMockSelected) return;
19117
19157
  if (!resolvedChatId || resolvedChatId.startsWith("mock-")) return;
19118
19158
  if (statusData?.status !== "active") return;
@@ -19126,7 +19166,7 @@ function AppInner() {
19126
19166
  }
19127
19167
  sendMessageMutation.mutate({ message: pending });
19128
19168
  }, [selectedWorkspaceId, resolvedChatId, isMockSelected, statusData?.status]);
19129
- const handleSelectWorkspace = useCallback8((workspaceId) => {
19169
+ const handleSelectWorkspace = useCallback9((workspaceId) => {
19130
19170
  setSelectedWorkspaceId(workspaceId);
19131
19171
  setSelectedChatId(null);
19132
19172
  setMockMessages([]);
@@ -19135,13 +19175,13 @@ function AppInner() {
19135
19175
  setLastViewedDiff(null);
19136
19176
  setFocusPanel("chat-input");
19137
19177
  }, []);
19138
- const handleFocus = useCallback8((panel) => {
19178
+ const handleFocus = useCallback9((panel) => {
19139
19179
  if (panel === "sidebar") {
19140
19180
  queryClient.invalidateQueries({ queryKey: ["workspaces"] });
19141
19181
  }
19142
19182
  setFocusPanel(panel);
19143
19183
  }, []);
19144
- const handleCreateWorkspace = useCallback8(
19184
+ const handleCreateWorkspace = useCallback9(
19145
19185
  async (group) => {
19146
19186
  if (group.id === "__ungrouped__") {
19147
19187
  toast.error("Cannot create a workspace in the Other / Ungrouped folder");
@@ -19159,7 +19199,7 @@ function AppInner() {
19159
19199
  },
19160
19200
  [createWorkspaceMutation, toast]
19161
19201
  );
19162
- const handleArchiveWorkspace = useCallback8(
19202
+ const handleArchiveWorkspace = useCallback9(
19163
19203
  async (workspaceId) => {
19164
19204
  try {
19165
19205
  await archiveWorkspaceMutation.mutateAsync(workspaceId);
@@ -19170,7 +19210,7 @@ function AppInner() {
19170
19210
  },
19171
19211
  [archiveWorkspaceMutation]
19172
19212
  );
19173
- const handleWakeWorkspace = useCallback8(
19213
+ const handleWakeWorkspace = useCallback9(
19174
19214
  async (workspaceId) => {
19175
19215
  setWakingWorkspaceId(workspaceId);
19176
19216
  try {
@@ -19184,7 +19224,7 @@ function AppInner() {
19184
19224
  },
19185
19225
  [wakeWorkspaceMutation, toast]
19186
19226
  );
19187
- const handleSelectChat = useCallback8((chatId) => {
19227
+ const handleSelectChat = useCallback9((chatId) => {
19188
19228
  setSelectedChatId(chatId);
19189
19229
  }, []);
19190
19230
  useKeyboard5((key) => {
@@ -19242,16 +19282,16 @@ function AppInner() {
19242
19282
  }
19243
19283
  if (focusPanel === "chat-input") return;
19244
19284
  });
19245
- const handleViewDiff = useCallback8((diff, repoName) => {
19285
+ const handleViewDiff = useCallback9((diff, repoName) => {
19246
19286
  setViewingDiff({ diff, repoName });
19247
19287
  setLastViewedDiff({ diff, repoName });
19248
19288
  setFocusPanel("chat-history");
19249
19289
  }, []);
19250
- const handleSelectChatMode = useCallback8(() => {
19290
+ const handleSelectChatMode = useCallback9(() => {
19251
19291
  setViewingDiff(null);
19252
19292
  setFocusPanel("chat-history");
19253
19293
  }, []);
19254
- const firstAvailableDiff = useMemo6(() => {
19294
+ const firstAvailableDiff = useMemo5(() => {
19255
19295
  if (lastViewedDiff) return lastViewedDiff;
19256
19296
  const repos2 = statusData?.repoStatuses ?? [];
19257
19297
  for (const repo of repos2) {
@@ -19261,14 +19301,14 @@ function AppInner() {
19261
19301
  }
19262
19302
  return null;
19263
19303
  }, [lastViewedDiff, statusData?.repoStatuses]);
19264
- const handleSelectDiffMode = useCallback8(() => {
19304
+ const handleSelectDiffMode = useCallback9(() => {
19265
19305
  if (firstAvailableDiff) {
19266
19306
  setViewingDiff(firstAvailableDiff);
19267
19307
  setLastViewedDiff(firstAvailableDiff);
19268
19308
  setFocusPanel("chat-history");
19269
19309
  }
19270
19310
  }, [firstAvailableDiff]);
19271
- const handleSendMessage = useCallback8(
19311
+ const handleSendMessage = useCallback9(
19272
19312
  async (message) => {
19273
19313
  if (!selectedWorkspaceId || !resolvedChatId) return;
19274
19314
  if (isMockSelected) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.340",
3
+ "version": "0.2.341",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {