replicas-cli 0.2.161 → 0.2.163

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.
@@ -1,3137 +0,0 @@
1
- #!/usr/bin/env bun
2
- import {
3
- SOURCE_CONFIG,
4
- buildGroups,
5
- createMockWorkspaceRecord,
6
- createUserMessage,
7
- extractPrNumber,
8
- filterDisplayMessages,
9
- getInitialChatId,
10
- getOrganizationId,
11
- getValidToken,
12
- isAgentBackendEvent,
13
- parseAgentEvents,
14
- parseSseChunk,
15
- parseUserMessage
16
- } from "./chunk-5N4AMDTY.mjs";
17
- import "./chunk-FFDYI4OH.mjs";
18
-
19
- // src/interactive/index.tsx
20
- import { createCliRenderer } from "@opentui/core";
21
- import { createRoot } from "@opentui/react";
22
-
23
- // src/interactive/App.tsx
24
- import { useState as useState8, useEffect as useEffect3, useMemo as useMemo6, useCallback as useCallback7, useRef as useRef5 } from "react";
25
- import { useKeyboard as useKeyboard5, useRenderer, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
26
- import { QueryClient } from "@tanstack/react-query";
27
-
28
- // ../shared/src/hooks/auth-context.ts
29
- import { createContext, useContext } from "react";
30
- var ReplicasAuthContext = createContext(null);
31
- var ReplicasAuthProvider = ReplicasAuthContext.Provider;
32
- function useReplicasAuth() {
33
- const ctx = useContext(ReplicasAuthContext);
34
- if (!ctx) {
35
- throw new Error("useReplicasAuth must be used within a ReplicasAuthProvider");
36
- }
37
- return ctx;
38
- }
39
-
40
- // ../shared/src/hooks/fetch.ts
41
- import { useCallback } from "react";
42
- var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
43
- var MAX_REDIRECTS = 5;
44
- async function fetchWithRedirects(url, init, attempt = 0) {
45
- const response = await fetch(url, { ...init, redirect: "manual" });
46
- if (REDIRECT_STATUSES.has(response.status)) {
47
- if (attempt >= MAX_REDIRECTS) {
48
- throw new Error("Too many redirects while contacting the Replicas API");
49
- }
50
- const location = response.headers.get("location");
51
- if (!location) {
52
- throw new Error(`Redirect status ${response.status} missing Location header`);
53
- }
54
- const nextUrl = new URL(location, url).toString();
55
- const shouldForceGet = response.status === 303 && init.method !== void 0 && init.method.toUpperCase() !== "GET";
56
- const nextInit = {
57
- ...init,
58
- method: shouldForceGet ? "GET" : init.method,
59
- body: shouldForceGet ? void 0 : init.body
60
- };
61
- return fetchWithRedirects(nextUrl, nextInit, attempt + 1);
62
- }
63
- return response;
64
- }
65
- function useOrgFetch() {
66
- const auth = useReplicasAuth();
67
- return useCallback(
68
- async function orgFetch(url, options) {
69
- const token = await auth.getAccessToken();
70
- const orgId = auth.getOrganizationId();
71
- const headers = {
72
- "Authorization": `Bearer ${token}`,
73
- "Replicas-Org-Id": orgId,
74
- "Content-Type": "application/json",
75
- ...options?.headers || {}
76
- };
77
- const absoluteUrl = `${auth.monolithUrl}${url}`;
78
- const requestInit = {
79
- ...options,
80
- headers,
81
- body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
82
- };
83
- const response = await fetchWithRedirects(absoluteUrl, requestInit);
84
- if (!response.ok) {
85
- const error = await response.json().catch(() => ({ error: "Request failed" }));
86
- throw new Error(error.error || `Request failed with status ${response.status}`);
87
- }
88
- return response.json();
89
- },
90
- [auth]
91
- );
92
- }
93
- function useRawOrgFetch() {
94
- const auth = useReplicasAuth();
95
- return useCallback(
96
- async function rawOrgFetch(url, options) {
97
- const token = await auth.getAccessToken();
98
- const orgId = auth.getOrganizationId();
99
- const headers = {
100
- "Authorization": `Bearer ${token}`,
101
- "Replicas-Org-Id": orgId,
102
- ...options?.headers || {}
103
- };
104
- const absoluteUrl = `${auth.monolithUrl}${url}`;
105
- return fetch(absoluteUrl, { ...options, headers });
106
- },
107
- [auth]
108
- );
109
- }
110
-
111
- // ../shared/src/hooks/useWorkspaces.ts
112
- import { useQuery, useMutation } from "@tanstack/react-query";
113
- function useWorkspaces(page = 1, limit = 100, scope = "organization") {
114
- const auth = useReplicasAuth();
115
- const orgFetch = useOrgFetch();
116
- const orgId = auth.getOrganizationId();
117
- return useQuery({
118
- queryKey: ["workspaces", orgId, scope, page, limit],
119
- queryFn: () => orgFetch(`/v1/workspaces?scope=${scope}&page=${page}&limit=${limit}`),
120
- enabled: !!orgId,
121
- staleTime: 1e4
122
- }, auth.queryClient);
123
- }
124
- function useCreateWorkspace() {
125
- const auth = useReplicasAuth();
126
- const orgFetch = useOrgFetch();
127
- const orgId = auth.getOrganizationId();
128
- return useMutation({
129
- mutationFn: (request) => orgFetch("/v1/workspaces", {
130
- method: "POST",
131
- body: request
132
- }),
133
- onSettled: () => {
134
- auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
135
- }
136
- }, auth.queryClient);
137
- }
138
- function useDeleteWorkspace() {
139
- const auth = useReplicasAuth();
140
- const orgFetch = useOrgFetch();
141
- const orgId = auth.getOrganizationId();
142
- return useMutation({
143
- mutationFn: (workspaceId) => orgFetch(`/v1/workspaces/${workspaceId}`, {
144
- method: "DELETE"
145
- }),
146
- onSuccess: (_data, deletedWorkspaceId) => {
147
- auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
148
- auth.queryClient.removeQueries({ queryKey: ["workspace-status", deletedWorkspaceId] });
149
- auth.queryClient.removeQueries({ queryKey: ["workspace-chats", deletedWorkspaceId] });
150
- auth.queryClient.removeQueries({ queryKey: ["chat-history", deletedWorkspaceId] });
151
- auth.queryClient.removeQueries({ queryKey: ["workspace-previews", deletedWorkspaceId] });
152
- }
153
- }, auth.queryClient);
154
- }
155
- function useWakeWorkspace() {
156
- const auth = useReplicasAuth();
157
- const orgFetch = useOrgFetch();
158
- const orgId = auth.getOrganizationId();
159
- return useMutation({
160
- mutationFn: (workspaceId) => orgFetch(`/v1/workspaces/${workspaceId}/wake`, {
161
- method: "POST"
162
- }),
163
- onSuccess: (_data, workspaceId) => {
164
- auth.queryClient.invalidateQueries({ queryKey: ["workspaces", orgId] });
165
- auth.queryClient.invalidateQueries({ queryKey: ["workspace-status", workspaceId] });
166
- auth.queryClient.invalidateQueries({ queryKey: ["workspace-chats", workspaceId] });
167
- }
168
- }, auth.queryClient);
169
- }
170
- function useGenerateWorkspaceName() {
171
- const auth = useReplicasAuth();
172
- const orgFetch = useOrgFetch();
173
- return useMutation({
174
- mutationFn: ({ workspaceId, prompt }) => orgFetch(`/v1/workspaces/${workspaceId}/generate-name`, {
175
- method: "POST",
176
- body: { prompt }
177
- }),
178
- onSuccess: () => {
179
- auth.queryClient.invalidateQueries({ queryKey: ["workspaces"] });
180
- }
181
- }, auth.queryClient);
182
- }
183
-
184
- // ../shared/src/hooks/useWorkspaceEngine.ts
185
- import { useCallback as useCallback2, useEffect, useMemo, useState } from "react";
186
- import { useQuery as useQuery2, useMutation as useMutation2 } from "@tanstack/react-query";
187
- function upsertChat(chats, chat) {
188
- const existingIndex = chats.findIndex((item) => item.id === chat.id);
189
- if (existingIndex === -1) {
190
- return [chat, ...chats].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
191
- }
192
- const next = [...chats];
193
- next[existingIndex] = chat;
194
- return next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
195
- }
196
- function patchChat(chats, chatId, patch) {
197
- let changed = false;
198
- const next = chats.map((chat) => {
199
- if (chat.id !== chatId) return chat;
200
- changed = true;
201
- return { ...chat, ...patch };
202
- });
203
- return changed ? next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) : chats;
204
- }
205
- function getEventSignature(value) {
206
- const normalize = (input) => {
207
- if (Array.isArray(input)) return input.map(normalize);
208
- if (input && typeof input === "object") {
209
- const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => [key, normalize(val)]);
210
- return Object.fromEntries(entries);
211
- }
212
- return input;
213
- };
214
- try {
215
- return JSON.stringify(normalize(value));
216
- } catch {
217
- return String(value);
218
- }
219
- }
220
- function useWorkspaceChats(workspaceId) {
221
- const auth = useReplicasAuth();
222
- const orgFetch = useOrgFetch();
223
- return useQuery2({
224
- queryKey: ["workspace-chats", workspaceId],
225
- queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats`),
226
- enabled: !!workspaceId,
227
- staleTime: 6e4
228
- }, auth.queryClient);
229
- }
230
- function useChatHistory(workspaceId, chatId) {
231
- const auth = useReplicasAuth();
232
- const orgFetch = useOrgFetch();
233
- return useQuery2({
234
- queryKey: ["chat-history", workspaceId, chatId],
235
- queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats/${chatId}/history`),
236
- enabled: !!workspaceId && !!chatId
237
- }, auth.queryClient);
238
- }
239
- function useSendChatMessage(workspaceId, chatId) {
240
- const auth = useReplicasAuth();
241
- const orgFetch = useOrgFetch();
242
- return useMutation2({
243
- mutationFn: (body) => {
244
- if (!workspaceId || !chatId) throw new Error("No chat selected");
245
- return orgFetch(
246
- `/v1/workspaces/${workspaceId}/chats/${chatId}/messages`,
247
- { method: "POST", body }
248
- );
249
- }
250
- }, auth.queryClient);
251
- }
252
- function useInterruptChat(workspaceId, chatId) {
253
- const auth = useReplicasAuth();
254
- const orgFetch = useOrgFetch();
255
- return useMutation2({
256
- mutationFn: () => {
257
- if (!workspaceId || !chatId) throw new Error("No chat selected");
258
- return orgFetch(
259
- `/v1/workspaces/${workspaceId}/chats/${chatId}/interrupt`,
260
- { method: "POST" }
261
- );
262
- }
263
- }, auth.queryClient);
264
- }
265
- function useWorkspaceStatus(workspaceId, options) {
266
- const auth = useReplicasAuth();
267
- const orgFetch = useOrgFetch();
268
- const includeDiffs = options?.includeDiffs ?? false;
269
- const enabled = options?.enabled ?? true;
270
- return useQuery2({
271
- queryKey: ["workspace-status", workspaceId, includeDiffs],
272
- queryFn: () => {
273
- const url = `/v1/workspaces/${workspaceId}/status${includeDiffs ? "?includeDiffs=true" : ""}`;
274
- return orgFetch(url);
275
- },
276
- enabled: !!workspaceId && enabled,
277
- staleTime: 3e3,
278
- retry: 2,
279
- placeholderData: (previousData) => previousData
280
- }, auth.queryClient);
281
- }
282
- function useWorkspacePreviews(workspaceId) {
283
- const auth = useReplicasAuth();
284
- const orgFetch = useOrgFetch();
285
- return useQuery2({
286
- queryKey: ["workspace-previews", workspaceId],
287
- queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/previews`),
288
- enabled: !!workspaceId,
289
- staleTime: 5e3,
290
- retry: 2
291
- }, auth.queryClient);
292
- }
293
- function useAgentAvailability(workspaceId) {
294
- const auth = useReplicasAuth();
295
- const orgFetch = useOrgFetch();
296
- return useQuery2({
297
- queryKey: ["agent-availability", workspaceId],
298
- queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/agent-availability`),
299
- enabled: !!workspaceId,
300
- staleTime: 6e4,
301
- refetchInterval: 6e4,
302
- retry: 2
303
- }, auth.queryClient);
304
- }
305
- function useWorkspaceEvents(workspaceId, enabled = true) {
306
- const auth = useReplicasAuth();
307
- const rawFetch = useRawOrgFetch();
308
- const qc = auth.queryClient;
309
- const [connected, setConnected] = useState(false);
310
- const handleEvent = useCallback2((event) => {
311
- if (!workspaceId) return;
312
- const chatsKey = ["workspace-chats", workspaceId];
313
- if (event.type === "chat.created" || event.type === "chat.updated") {
314
- if (event.payload.chat.parentChatId === null) {
315
- qc.setQueryData(chatsKey, (current) => {
316
- if (!current) return { chats: [event.payload.chat] };
317
- return { chats: upsertChat(current.chats, event.payload.chat) };
318
- });
319
- }
320
- return;
321
- }
322
- if (event.type === "chat.deleted") {
323
- qc.setQueryData(chatsKey, (current) => {
324
- if (!current) return current;
325
- return { chats: current.chats.filter((chat) => chat.id !== event.payload.chatId) };
326
- });
327
- qc.removeQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
328
- return;
329
- }
330
- if (event.type === "chat.turn.accepted") {
331
- if (!event.payload.queued) {
332
- qc.setQueryData(chatsKey, (current) => {
333
- if (!current) return current;
334
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
335
- });
336
- }
337
- return;
338
- }
339
- if (event.type === "chat.turn.started") {
340
- qc.setQueryData(chatsKey, (current) => {
341
- if (!current) return current;
342
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
343
- });
344
- return;
345
- }
346
- if (event.type === "chat.turn.completed" || event.type === "chat.interrupted") {
347
- qc.setQueryData(chatsKey, (current) => {
348
- if (!current) return current;
349
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: false, updatedAt: event.ts }) };
350
- });
351
- qc.invalidateQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
352
- return;
353
- }
354
- if (event.type === "chat.turn.delta") {
355
- qc.setQueryData(
356
- ["chat-history", workspaceId, event.payload.chatId],
357
- (current) => {
358
- const incomingSignature = getEventSignature(event.payload.event);
359
- if (!current) {
360
- return { thread_id: null, events: [event.payload.event] };
361
- }
362
- const hasDuplicate = current.events.some((e) => getEventSignature(e) === incomingSignature);
363
- if (hasDuplicate) return current;
364
- return { ...current, events: [...current.events, event.payload.event] };
365
- }
366
- );
367
- return;
368
- }
369
- if (event.type === "repo.status.changed") {
370
- qc.setQueryData(["workspace-status", workspaceId, false], (current) => {
371
- if (!current) return current;
372
- return { ...current, repoStatuses: event.payload.repos };
373
- });
374
- qc.invalidateQueries({ queryKey: ["workspace-status", workspaceId, true] });
375
- return;
376
- }
377
- if (event.type === "preview.changed") {
378
- qc.setQueryData(["workspace-previews", workspaceId], {
379
- previews: event.payload.previews
380
- });
381
- return;
382
- }
383
- }, [qc, workspaceId]);
384
- useEffect(() => {
385
- if (!workspaceId || !enabled) return;
386
- const controller = new AbortController();
387
- let cancelled = false;
388
- const connect = async () => {
389
- try {
390
- const response = await rawFetch(`/v1/workspaces/${workspaceId}/events`, {
391
- headers: { "Accept": "text/event-stream" },
392
- signal: controller.signal
393
- });
394
- if (!response.ok || !response.body) {
395
- setConnected(false);
396
- return false;
397
- }
398
- setConnected(true);
399
- const reader = response.body.getReader();
400
- const decoder = new TextDecoder();
401
- let buffer = "";
402
- while (!cancelled) {
403
- const { done, value } = await reader.read();
404
- if (done) return false;
405
- buffer += decoder.decode(value, { stream: true });
406
- const chunks = buffer.split("\n\n");
407
- buffer = chunks.pop() ?? "";
408
- for (const chunk of chunks) {
409
- const event = parseSseChunk(chunk);
410
- if (event) handleEvent(event);
411
- }
412
- }
413
- return false;
414
- } catch {
415
- setConnected(false);
416
- return false;
417
- }
418
- };
419
- const run = async () => {
420
- while (!cancelled) {
421
- await connect();
422
- if (cancelled) break;
423
- await new Promise((resolve) => setTimeout(resolve, 1e3));
424
- }
425
- };
426
- run().catch(() => setConnected(false));
427
- return () => {
428
- cancelled = true;
429
- controller.abort();
430
- setConnected(false);
431
- };
432
- }, [enabled, handleEvent, rawFetch, workspaceId]);
433
- return useMemo(() => ({ connected }), [connected]);
434
- }
435
-
436
- // ../shared/src/hooks/useRepositories.ts
437
- import { useQuery as useQuery3 } from "@tanstack/react-query";
438
- function useRepositorySets() {
439
- const auth = useReplicasAuth();
440
- const orgFetch = useOrgFetch();
441
- const orgId = auth.getOrganizationId();
442
- return useQuery3({
443
- queryKey: ["repository-sets", orgId],
444
- queryFn: () => orgFetch("/v1/repository-sets"),
445
- enabled: !!orgId
446
- }, auth.queryClient);
447
- }
448
-
449
- // ../shared/src/hooks/useEnvironment.ts
450
- import { useQuery as useQuery4 } from "@tanstack/react-query";
451
- function useEnvironments() {
452
- const auth = useReplicasAuth();
453
- const orgFetch = useOrgFetch();
454
- const orgId = auth.getOrganizationId();
455
- return useQuery4({
456
- queryKey: ["environments", orgId],
457
- queryFn: () => orgFetch("/v1/environments"),
458
- enabled: !!orgId
459
- }, auth.queryClient);
460
- }
461
-
462
- // src/interactive/components/StatusBar.tsx
463
- import { jsx, jsxs } from "@opentui/react/jsx-runtime";
464
- var KEYBINDS = {
465
- sidebar: "j/k nav \u21B5 select d del a add w wake",
466
- "chat-tabs": "\u2190/\u2192 switch tabs",
467
- "chat-history": "j/k scroll",
468
- "chat-input": "\u21B5 send \u21E5 plan/build",
469
- info: "j/k nav \u21B5 open o dashboard w wake"
470
- };
471
- var DIFF_KEYBINDS = "j/k nav \u21E5 switch pane";
472
- var MODE_HINT = "1 chat 2 diff";
473
- var MODE_HINT_PANELS = /* @__PURE__ */ new Set([
474
- "chat-tabs",
475
- "chat-history",
476
- "info"
477
- ]);
478
- function StatusBar({ focusPanel, viewingDiff, hasDiffAvailable }) {
479
- const showingDiffHints = viewingDiff && focusPanel !== "sidebar" && focusPanel !== "info";
480
- const baseHints = showingDiffHints ? DIFF_KEYBINDS : KEYBINDS[focusPanel];
481
- const showModeHint = hasDiffAvailable && (showingDiffHints || MODE_HINT_PANELS.has(focusPanel));
482
- const hints = showModeHint ? `${baseHints} ${MODE_HINT}` : baseHints;
483
- return /* @__PURE__ */ jsxs(
484
- "box",
485
- {
486
- height: 1,
487
- backgroundColor: "#111111",
488
- paddingX: 1,
489
- flexDirection: "row",
490
- justifyContent: "space-between",
491
- width: "100%",
492
- children: [
493
- /* @__PURE__ */ jsx("text", { children: /* @__PURE__ */ jsx("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx("strong", { children: "Replicas" }) }) }),
494
- /* @__PURE__ */ jsxs("text", { children: [
495
- /* @__PURE__ */ jsxs("span", { fg: "#555555", children: [
496
- hints,
497
- " "
498
- ] }),
499
- /* @__PURE__ */ jsxs("span", { fg: "#888888", children: [
500
- "\u21E7",
501
- "Tab"
502
- ] }),
503
- /* @__PURE__ */ jsx("span", { fg: "#555555", children: " panels " }),
504
- /* @__PURE__ */ jsx("span", { fg: "#888888", children: "^C" }),
505
- /* @__PURE__ */ jsx("span", { fg: "#555555", children: " quit" })
506
- ] })
507
- ]
508
- }
509
- );
510
- }
511
-
512
- // src/interactive/components/WorkspaceSidebar.tsx
513
- import { useState as useState2, useMemo as useMemo2, useCallback as useCallback3 } from "react";
514
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
515
- import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
516
- function flattenGroups(groups, collapsedGroups) {
517
- const items = [];
518
- for (const group of groups) {
519
- if (group.id === "__ungrouped__" && group.workspaces.length === 0) continue;
520
- const expanded = !collapsedGroups.has(group.id);
521
- items.push({
522
- type: "group",
523
- id: group.id,
524
- name: group.name,
525
- isSetBound: !!group.repoSetIdForCreate,
526
- expanded
527
- });
528
- if (expanded) {
529
- for (const ws of group.workspaces) {
530
- items.push({
531
- type: "workspace",
532
- workspaceId: ws.id,
533
- name: ws.name,
534
- status: ws.status,
535
- isMock: ws.status === "preparing"
536
- });
537
- }
538
- if (group.id !== "__ungrouped__") {
539
- items.push({ type: "add", group });
540
- }
541
- }
542
- }
543
- return items;
544
- }
545
- function truncate(text, maxLen) {
546
- return text.length > maxLen ? text.slice(0, maxLen - 1) + "\u2026" : text;
547
- }
548
- function WorkspaceSidebar({
549
- groups,
550
- selectedWorkspaceId,
551
- mockIds,
552
- onSelectWorkspace,
553
- onCreateWorkspace,
554
- onDeleteWorkspace,
555
- onWakeWorkspace,
556
- wakingWorkspaceId,
557
- focused,
558
- loading
559
- }) {
560
- const { height: termHeight } = useTerminalDimensions();
561
- const [collapsedGroups, setCollapsedGroups] = useState2(/* @__PURE__ */ new Set());
562
- const [cursorIndex, setCursorIndex] = useState2(0);
563
- const [scrollOffset, setScrollOffset] = useState2(0);
564
- const [confirmDelete, setConfirmDelete] = useState2(null);
565
- const items = useMemo2(() => flattenGroups(groups, collapsedGroups), [groups, collapsedGroups]);
566
- const clampedCursor = Math.min(cursorIndex, Math.max(0, items.length - 1));
567
- if (clampedCursor !== cursorIndex) {
568
- setCursorIndex(clampedCursor);
569
- }
570
- const visibleHeight = Math.max(1, termHeight - 4);
571
- const adjustScroll = useCallback3(
572
- (newCursor) => {
573
- let newOffset = scrollOffset;
574
- if (newCursor < newOffset) {
575
- newOffset = newCursor;
576
- } else if (newCursor >= newOffset + visibleHeight) {
577
- newOffset = newCursor - visibleHeight + 1;
578
- }
579
- setScrollOffset(newOffset);
580
- },
581
- [scrollOffset, visibleHeight]
582
- );
583
- const moveCursor = useCallback3(
584
- (next) => {
585
- const len = items.length;
586
- if (len === 0) return;
587
- const wrapped = (next % len + len) % len;
588
- setCursorIndex(wrapped);
589
- adjustScroll(wrapped);
590
- },
591
- [items.length, adjustScroll]
592
- );
593
- const handleAction = useCallback3(
594
- (item) => {
595
- if (item.type === "group") {
596
- setCollapsedGroups((prev) => {
597
- const next = new Set(prev);
598
- if (next.has(item.id)) {
599
- next.delete(item.id);
600
- } else {
601
- next.add(item.id);
602
- }
603
- return next;
604
- });
605
- } else if (item.type === "workspace") {
606
- onSelectWorkspace(item.workspaceId);
607
- } else if (item.type === "add") {
608
- onCreateWorkspace(item.group);
609
- }
610
- },
611
- [onSelectWorkspace, onCreateWorkspace]
612
- );
613
- const handleDelete = useCallback3(() => {
614
- const item = items[cursorIndex];
615
- if (!item || item.type !== "workspace") return;
616
- if (item.isMock) return;
617
- if (confirmDelete === item.workspaceId) {
618
- onDeleteWorkspace(item.workspaceId);
619
- setConfirmDelete(null);
620
- } else {
621
- setConfirmDelete(item.workspaceId);
622
- }
623
- }, [items, cursorIndex, confirmDelete, onDeleteWorkspace]);
624
- const moveCursorAndClearConfirm = useCallback3(
625
- (next) => {
626
- setConfirmDelete(null);
627
- moveCursor(next);
628
- },
629
- [moveCursor]
630
- );
631
- useKeyboard((key) => {
632
- if (!focused) return;
633
- if (confirmDelete) {
634
- if (key.name === "y") {
635
- onDeleteWorkspace(confirmDelete);
636
- setConfirmDelete(null);
637
- return;
638
- }
639
- if (key.name === "n" || key.name === "escape") {
640
- setConfirmDelete(null);
641
- return;
642
- }
643
- return;
644
- }
645
- if (key.name === "up" || key.name === "k") {
646
- moveCursorAndClearConfirm(cursorIndex - 1);
647
- return;
648
- }
649
- if (key.name === "down" || key.name === "j") {
650
- moveCursorAndClearConfirm(cursorIndex + 1);
651
- return;
652
- }
653
- if (key.name === "g" && !key.shift) {
654
- moveCursorAndClearConfirm(0);
655
- return;
656
- }
657
- if (key.name === "g" && key.shift) {
658
- moveCursorAndClearConfirm(items.length - 1);
659
- return;
660
- }
661
- if (key.name === "enter" || key.name === "return" || key.name === "l") {
662
- const item = items[cursorIndex];
663
- if (item) handleAction(item);
664
- return;
665
- }
666
- if (key.name === "h") {
667
- const item = items[cursorIndex];
668
- if (item?.type === "group" && item.expanded) {
669
- handleAction(item);
670
- }
671
- return;
672
- }
673
- if (key.name === "d" || key.name === "x" || key.name === "delete") {
674
- handleDelete();
675
- return;
676
- }
677
- if (key.name === "a") {
678
- const item = items[cursorIndex];
679
- if (item?.type === "add") {
680
- onCreateWorkspace(item.group);
681
- }
682
- return;
683
- }
684
- if (key.name === "w") {
685
- const item = items[cursorIndex];
686
- if (item?.type === "workspace" && item.status === "sleeping" && wakingWorkspaceId !== item.workspaceId) {
687
- onWakeWorkspace(item.workspaceId);
688
- }
689
- return;
690
- }
691
- });
692
- const handleItemClick = useCallback3(
693
- (globalIndex) => {
694
- setConfirmDelete(null);
695
- setCursorIndex(globalIndex);
696
- const item = items[globalIndex];
697
- if (item) handleAction(item);
698
- },
699
- [items, handleAction]
700
- );
701
- const handleDeleteClick = useCallback3(
702
- (workspaceId, e) => {
703
- e?.preventDefault?.();
704
- if (confirmDelete === workspaceId) {
705
- onDeleteWorkspace(workspaceId);
706
- setConfirmDelete(null);
707
- } else {
708
- setConfirmDelete(workspaceId);
709
- }
710
- },
711
- [confirmDelete, onDeleteWorkspace]
712
- );
713
- const visibleItems = items.slice(scrollOffset, scrollOffset + visibleHeight);
714
- if (loading) {
715
- return /* @__PURE__ */ jsx2(
716
- "box",
717
- {
718
- width: 28,
719
- border: true,
720
- borderStyle: "rounded",
721
- borderColor: "#333333",
722
- title: "Workspaces",
723
- titleAlignment: "center",
724
- flexDirection: "column",
725
- backgroundColor: "#000000",
726
- children: /* @__PURE__ */ jsx2("text", { fg: "#666666", children: "Loading..." })
727
- }
728
- );
729
- }
730
- return /* @__PURE__ */ jsxs2(
731
- "box",
732
- {
733
- width: 28,
734
- border: true,
735
- borderStyle: "rounded",
736
- borderColor: focused ? "#66bb6a" : "#333333",
737
- title: "Workspaces",
738
- titleAlignment: "center",
739
- flexDirection: "column",
740
- backgroundColor: "#000000",
741
- children: [
742
- items.length === 0 ? /* @__PURE__ */ jsx2("text", { fg: "#666666", paddingX: 1, children: "No repositories" }) : visibleItems.map((item, vi) => {
743
- const globalIndex = scrollOffset + vi;
744
- const isCursor = globalIndex === cursorIndex;
745
- if (item.type === "group") {
746
- const chevron = item.expanded ? "\u25BE" : "\u25B8";
747
- const label = truncate(item.name, 20);
748
- const suffix = item.isSetBound ? " Set" : "";
749
- return /* @__PURE__ */ jsx2(
750
- "box",
751
- {
752
- height: 1,
753
- backgroundColor: isCursor ? "#1a1a1a" : "#151515",
754
- paddingX: 1,
755
- onMouseDown: () => handleItemClick(globalIndex),
756
- children: /* @__PURE__ */ jsxs2("text", { children: [
757
- /* @__PURE__ */ jsxs2("span", { fg: isCursor ? "#ffffff" : "#888888", children: [
758
- chevron,
759
- " "
760
- ] }),
761
- /* @__PURE__ */ jsx2("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx2("strong", { children: label }) }),
762
- suffix && /* @__PURE__ */ jsxs2("span", { fg: "#555555", children: [
763
- " ",
764
- suffix
765
- ] })
766
- ] })
767
- },
768
- `g-${item.id}`
769
- );
770
- }
771
- if (item.type === "workspace") {
772
- const isSelected = item.workspaceId === selectedWorkspaceId;
773
- const isConfirming = confirmDelete === item.workspaceId;
774
- const dot = item.status === "active" ? "\u25CF" : item.status === "preparing" ? "\u25CC" : "\u25CB";
775
- const dotColor = item.status === "active" ? "#66bb6a" : item.status === "preparing" ? "#ffaa00" : "#666666";
776
- const nameColor = isSelected ? "#66bb6a" : "#cccccc";
777
- const itemBg = isConfirming ? "#331111" : isCursor ? "#1a1a1a" : isSelected ? "#0a1a0a" : "#0a0a0a";
778
- if (isConfirming) {
779
- return /* @__PURE__ */ jsxs2("box", { height: 1, backgroundColor: "#331111", paddingX: 1, flexDirection: "row", gap: 1, children: [
780
- /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#ff4444", children: "Delete?" }) }),
781
- /* @__PURE__ */ jsx2("box", { onMouseDown: () => {
782
- onDeleteWorkspace(item.workspaceId);
783
- setConfirmDelete(null);
784
- }, children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#66bb6a", children: "[y]" }) }) }),
785
- /* @__PURE__ */ jsx2("box", { onMouseDown: () => setConfirmDelete(null), children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: "#ff4444", children: "[n]" }) }) })
786
- ] }, `w-${item.workspaceId}`);
787
- }
788
- return /* @__PURE__ */ jsxs2(
789
- "box",
790
- {
791
- height: 1,
792
- backgroundColor: itemBg,
793
- paddingX: 1,
794
- flexDirection: "row",
795
- justifyContent: "space-between",
796
- onMouseDown: () => handleItemClick(globalIndex),
797
- children: [
798
- /* @__PURE__ */ jsxs2("text", { children: [
799
- /* @__PURE__ */ jsxs2("span", { fg: dotColor, children: [
800
- " ",
801
- dot,
802
- " "
803
- ] }),
804
- /* @__PURE__ */ jsx2("span", { fg: nameColor, children: truncate(item.name, 17) })
805
- ] }),
806
- !item.isMock && /* @__PURE__ */ jsx2("box", { onMouseDown: (e) => handleDeleteClick(item.workspaceId, e), children: /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#ff4444" : "#222222", children: "\u2717" }) }) })
807
- ]
808
- },
809
- `w-${item.workspaceId}`
810
- );
811
- }
812
- return /* @__PURE__ */ jsx2(
813
- "box",
814
- {
815
- height: 1,
816
- backgroundColor: isCursor ? "#1a1a1a" : "#0a0a0a",
817
- paddingX: 1,
818
- onMouseDown: () => handleItemClick(globalIndex),
819
- children: /* @__PURE__ */ jsxs2("text", { children: [
820
- /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#66bb6a" : "#444444", children: " + " }),
821
- /* @__PURE__ */ jsx2("span", { fg: isCursor ? "#888888" : "#333333", children: "New workspace" })
822
- ] })
823
- },
824
- `a-${item.group.id}`
825
- );
826
- }),
827
- focused && /* @__PURE__ */ jsx2("box", { height: 1, paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsxs2("text", { children: [
828
- /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "j/k:nav " }),
829
- /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "d:del " }),
830
- /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "a:add " }),
831
- /* @__PURE__ */ jsx2("span", { fg: "#555555", children: "w:wake" })
832
- ] }) })
833
- ]
834
- }
835
- );
836
- }
837
-
838
- // src/interactive/components/ChatArea.tsx
839
- import { useRef as useRef2, useEffect as useEffect2 } from "react";
840
- import { useKeyboard as useKeyboard3 } from "@opentui/react";
841
-
842
- // src/interactive/components/ChatMessage.tsx
843
- import { useState as useState5, useMemo as useMemo4 } from "react";
844
-
845
- // src/interactive/components/Spinner.tsx
846
- import "opentui-spinner/react";
847
- import { createPulse } from "opentui-spinner";
848
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
849
- var thinkingColor = createPulse(["#66bb6a", "#4caf50", "#2e7d32"], 0.5);
850
- function SpinnerLabel({ color, label }) {
851
- return /* @__PURE__ */ jsxs3("box", { flexDirection: "row", gap: 1, children: [
852
- /* @__PURE__ */ jsx3("spinner", { name: "dots", color: color ?? thinkingColor, interval: 80 }),
853
- label && /* @__PURE__ */ jsx3("text", { children: /* @__PURE__ */ jsx3("span", { fg: color ?? "#66bb6a", children: label }) })
854
- ] });
855
- }
856
-
857
- // src/interactive/components/StructuredUserMessage.tsx
858
- import { useState as useState4 } from "react";
859
-
860
- // src/interactive/components/diff-utils.tsx
861
- import React2, { useState as useState3, useMemo as useMemo3, useCallback as useCallback4, useRef } from "react";
862
- import { SyntaxStyle } from "@opentui/core";
863
- import { useKeyboard as useKeyboard2 } from "@opentui/react";
864
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
865
- var sharedSyntaxStyle = null;
866
- function getSyntaxStyle() {
867
- if (!sharedSyntaxStyle) {
868
- sharedSyntaxStyle = SyntaxStyle.fromTheme([
869
- { scope: ["markup.heading", "markup.heading.1", "markup.heading.2", "markup.heading.3"], style: { foreground: "#66bb6a", bold: true } },
870
- { scope: ["markup.bold", "markup.strong"], style: { foreground: "#ffffff", bold: true } },
871
- { scope: ["markup.italic", "markup.emphasis"], style: { foreground: "#e0e0e0", italic: true } },
872
- { scope: ["markup.link"], style: { foreground: "#7dcfff", underline: true } },
873
- { scope: ["markup.link.url"], style: { foreground: "#7dcfff", underline: true } },
874
- { scope: ["markup.link.text"], style: { foreground: "#66bb6a", underline: true } },
875
- { scope: ["markup.link.label"], style: { foreground: "#66bb6a", underline: true } },
876
- { scope: ["markup.list"], style: { foreground: "#66bb6a" } },
877
- { scope: ["markup.quote"], style: { foreground: "#888888", italic: true } },
878
- { scope: ["markup.raw", "markup.raw.block"], style: { foreground: "#bb9af7" } },
879
- { scope: ["markup.raw.inline"], style: { foreground: "#bb9af7", background: "#1a1a2e" } },
880
- { scope: ["keyword", "keyword.control", "keyword.operator"], style: { foreground: "#bb9af7" } },
881
- { scope: ["string", "string.quoted"], style: { foreground: "#9ece6a" } },
882
- { scope: ["comment", "comment.line", "comment.block"], style: { foreground: "#555555", italic: true } },
883
- { scope: ["constant", "constant.numeric", "constant.language"], style: { foreground: "#ff9e64" } },
884
- { scope: ["entity.name.function", "support.function"], style: { foreground: "#7aa2f7" } },
885
- { scope: ["entity.name.type", "support.type"], style: { foreground: "#2ac3de" } },
886
- { scope: ["variable", "variable.other"], style: { foreground: "#c0caf5" } },
887
- { scope: ["punctuation"], style: { foreground: "#888888" } }
888
- ]);
889
- }
890
- return sharedSyntaxStyle;
891
- }
892
- function truncateName(text, maxLen) {
893
- if (maxLen <= 0) return "";
894
- if (text.length <= maxLen) return text;
895
- if (maxLen === 1) return "\u2026";
896
- return text.slice(0, maxLen - 1) + "\u2026";
897
- }
898
- function countDiffStats(diff) {
899
- let added = 0;
900
- let removed = 0;
901
- for (const line of diff.split("\n")) {
902
- if (line.startsWith("+") && !line.startsWith("+++")) added++;
903
- else if (line.startsWith("-") && !line.startsWith("---")) removed++;
904
- }
905
- return { added, removed };
906
- }
907
- var EXT_TO_FILETYPE = {
908
- ts: "typescript",
909
- tsx: "typescriptreact",
910
- js: "javascript",
911
- jsx: "javascriptreact",
912
- cts: "typescript",
913
- mts: "typescript",
914
- cjs: "javascript",
915
- mjs: "javascript",
916
- py: "python",
917
- rs: "rust",
918
- go: "go",
919
- rb: "ruby",
920
- java: "java",
921
- kt: "kotlin",
922
- swift: "swift",
923
- c: "c",
924
- cpp: "cpp",
925
- h: "c",
926
- hpp: "cpp",
927
- cs: "csharp",
928
- css: "css",
929
- scss: "scss",
930
- html: "html",
931
- json: "json",
932
- yaml: "yaml",
933
- yml: "yaml",
934
- toml: "toml",
935
- md: "markdown",
936
- sh: "bash",
937
- bash: "bash",
938
- zsh: "bash",
939
- sql: "sql"
940
- };
941
- function extFromPath(path) {
942
- const ext = path.split(".").pop()?.toLowerCase();
943
- return ext ? EXT_TO_FILETYPE[ext] : void 0;
944
- }
945
- function ensureUnifiedHeaders(diff, filePath) {
946
- if (/^--- /m.test(diff) && /^@@ /m.test(diff)) return diff;
947
- const file = filePath ?? "a";
948
- const fileHeaders = `--- a/${file}
949
- +++ b/${file}
950
- `;
951
- if (/^@@ -\d+/m.test(diff)) {
952
- return fileHeaders + diff;
953
- }
954
- const lines = diff.split("\n");
955
- const diffLines = lines.filter((l) => !l.startsWith("@@"));
956
- let oldCount = 0;
957
- let newCount = 0;
958
- for (const line of diffLines) {
959
- if (line.startsWith("+") && !line.startsWith("+++")) newCount++;
960
- else if (line.startsWith("-") && !line.startsWith("---")) oldCount++;
961
- else if (line.startsWith(" ") || !line.startsWith("+") && !line.startsWith("-") && line.length > 0) {
962
- oldCount++;
963
- newCount++;
964
- }
965
- }
966
- const hunk = `@@ -1,${oldCount} +1,${newCount} @@`;
967
- return `${fileHeaders}${hunk}
968
- ${diffLines.join("\n")}`;
969
- }
970
- function diffProps(filePath) {
971
- return {
972
- view: "split",
973
- filetype: filePath ? extFromPath(filePath) : void 0,
974
- syntaxStyle: getSyntaxStyle(),
975
- showLineNumbers: true,
976
- width: "100%",
977
- wrapMode: "word",
978
- fg: "#cccccc",
979
- addedBg: "#0d2b0d",
980
- removedBg: "#2b0d0d",
981
- contextBg: "#0a0a0a",
982
- addedSignColor: "#66bb6a",
983
- removedSignColor: "#ff4444",
984
- lineNumberFg: "#555555",
985
- lineNumberBg: "#0a0a0a",
986
- addedLineNumberBg: "#0d2b0d",
987
- removedLineNumberBg: "#2b0d0d"
988
- };
989
- }
990
- function DiffView({ diff, filePath }) {
991
- const hunks = useMemo3(() => splitDiffByHunks(diff), [diff]);
992
- const props = diffProps(filePath);
993
- if (hunks.length <= 1) {
994
- return /* @__PURE__ */ jsx4("diff", { diff: ensureUnifiedHeaders(diff, filePath), ...props });
995
- }
996
- return /* @__PURE__ */ jsx4("box", { flexDirection: "column", children: hunks.map((hunk, i) => {
997
- const prev = i > 0 ? hunks[i - 1] : null;
998
- const gap = prev ? hunk.newStart - (prev.newStart + prev.newCount) : 0;
999
- return /* @__PURE__ */ jsxs4(React2.Fragment, { children: [
1000
- prev && gap > 0 && /* @__PURE__ */ jsx4("box", { backgroundColor: "#151515", paddingX: 1, height: 1, children: /* @__PURE__ */ jsx4("text", { children: /* @__PURE__ */ jsxs4("span", { fg: "#666666", children: [
1001
- gap,
1002
- " unmodified ",
1003
- gap === 1 ? "line" : "lines"
1004
- ] }) }) }),
1005
- /* @__PURE__ */ jsx4("diff", { diff: ensureUnifiedHeaders(hunk.body, filePath), ...props })
1006
- ] }, i);
1007
- }) });
1008
- }
1009
- function splitDiffByFile(fullDiff) {
1010
- const chunks = [];
1011
- const lines = fullDiff.split("\n");
1012
- let currentLines = [];
1013
- let currentPath = "";
1014
- for (const line of lines) {
1015
- if (line.startsWith("diff --git ")) {
1016
- if (currentPath && currentLines.length > 0) {
1017
- const diff = currentLines.join("\n");
1018
- chunks.push({ path: currentPath, diff, ...countDiffStats(diff) });
1019
- }
1020
- const match = line.match(/^diff --git a\/(.+) b\/(.+)$/);
1021
- currentPath = match ? match[2] : "";
1022
- currentLines = [line];
1023
- } else {
1024
- currentLines.push(line);
1025
- }
1026
- }
1027
- if (currentPath && currentLines.length > 0) {
1028
- const diff = currentLines.join("\n");
1029
- chunks.push({ path: currentPath, diff, ...countDiffStats(diff) });
1030
- }
1031
- return chunks;
1032
- }
1033
- function splitDiffByHunks(fileDiff) {
1034
- const hunks = [];
1035
- const lines = fileDiff.split("\n");
1036
- let current = null;
1037
- for (const line of lines) {
1038
- const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
1039
- if (match) {
1040
- if (current) hunks.push({ body: current.lines.join("\n"), newStart: current.newStart, newCount: current.newCount });
1041
- current = {
1042
- lines: [line],
1043
- newStart: parseInt(match[1], 10),
1044
- newCount: match[2] ? parseInt(match[2], 10) : 1
1045
- };
1046
- } else if (current) {
1047
- current.lines.push(line);
1048
- }
1049
- }
1050
- if (current) hunks.push({ body: current.lines.join("\n"), newStart: current.newStart, newCount: current.newCount });
1051
- return hunks;
1052
- }
1053
- function buildFileTree(files) {
1054
- const root = { name: "", children: /* @__PURE__ */ new Map() };
1055
- files.forEach((file, idx) => {
1056
- const segments = file.path.split("/");
1057
- let node = root;
1058
- for (const seg of segments) {
1059
- if (!node.children.has(seg)) {
1060
- node.children.set(seg, { name: seg, children: /* @__PURE__ */ new Map() });
1061
- }
1062
- node = node.children.get(seg);
1063
- }
1064
- node.fileIndex = idx;
1065
- node.added = file.added;
1066
- node.removed = file.removed;
1067
- });
1068
- const result = [];
1069
- function walk(node, depth, accumulatedPath, parentPath) {
1070
- if (node.fileIndex !== void 0) {
1071
- const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
1072
- result.push({
1073
- type: "file",
1074
- name: node.name,
1075
- path: fullPath,
1076
- depth,
1077
- fileIndex: node.fileIndex,
1078
- added: node.added,
1079
- removed: node.removed
1080
- });
1081
- return;
1082
- }
1083
- const children = Array.from(node.children.values());
1084
- if (children.length === 1 && children[0].fileIndex === void 0) {
1085
- walk(children[0], depth, [...accumulatedPath, node.name], parentPath);
1086
- return;
1087
- }
1088
- let folderFullPath = parentPath;
1089
- if (node.name !== "") {
1090
- const displayName = [...accumulatedPath, node.name].filter(Boolean).join("/");
1091
- folderFullPath = parentPath ? `${parentPath}/${displayName}` : displayName;
1092
- result.push({ type: "folder", name: displayName, path: folderFullPath, depth });
1093
- }
1094
- const childDepth = node.name === "" ? depth : depth + 1;
1095
- for (const child of children) {
1096
- walk(child, childDepth, [], folderFullPath);
1097
- }
1098
- }
1099
- walk(root, 0, [], "");
1100
- return result;
1101
- }
1102
- var FILE_PANEL_WIDTH = 32;
1103
- function DiffViewer({ diff, repoName, focused }) {
1104
- const files = useMemo3(() => splitDiffByFile(diff), [diff]);
1105
- const tree = useMemo3(() => buildFileTree(files), [files]);
1106
- const [selectedFileIndex, setSelectedFileIndex] = useState3(0);
1107
- const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
1108
- const [cursorIndex, setCursorIndex] = useState3(0);
1109
- const [innerFocus, setInnerFocus] = useState3("files");
1110
- const safeFileIndex = Math.min(selectedFileIndex, Math.max(0, files.length - 1));
1111
- const diffScrollRef = useRef(null);
1112
- const fileTreeScrollRef = useRef(null);
1113
- const visibleTree = useMemo3(() => {
1114
- const result = [];
1115
- let hideUntilDepth = null;
1116
- for (const node of tree) {
1117
- if (hideUntilDepth !== null && node.depth <= hideUntilDepth) {
1118
- hideUntilDepth = null;
1119
- }
1120
- if (hideUntilDepth !== null) continue;
1121
- result.push(node);
1122
- if (node.type === "folder" && collapsedFolders.has(node.path)) {
1123
- hideUntilDepth = node.depth;
1124
- }
1125
- }
1126
- return result;
1127
- }, [tree, collapsedFolders]);
1128
- const safeCursorIndex = Math.min(cursorIndex, Math.max(0, visibleTree.length - 1));
1129
- const scrollCursorIntoView = useCallback4((treeIdx) => {
1130
- const node = visibleTree[treeIdx];
1131
- if (!node) return;
1132
- fileTreeScrollRef.current?.scrollChildIntoView(`tree-${node.path}`);
1133
- }, [visibleTree]);
1134
- const moveCursor = useCallback4((next) => {
1135
- const len = visibleTree.length;
1136
- if (len === 0) return;
1137
- const wrapped = (next % len + len) % len;
1138
- setCursorIndex(wrapped);
1139
- scrollCursorIntoView(wrapped);
1140
- const node = visibleTree[wrapped];
1141
- if (node?.type === "file" && node.fileIndex !== void 0) {
1142
- setSelectedFileIndex(node.fileIndex);
1143
- }
1144
- }, [visibleTree, scrollCursorIntoView]);
1145
- const toggleFolder = useCallback4((folderPath) => {
1146
- setCollapsedFolders((prev) => {
1147
- const next = new Set(prev);
1148
- if (next.has(folderPath)) next.delete(folderPath);
1149
- else next.add(folderPath);
1150
- return next;
1151
- });
1152
- }, []);
1153
- const selectFile = useCallback4((fileIndex, treeIndex) => {
1154
- setSelectedFileIndex(fileIndex);
1155
- setCursorIndex(treeIndex);
1156
- scrollCursorIntoView(treeIndex);
1157
- }, [scrollCursorIntoView]);
1158
- useKeyboard2((key) => {
1159
- if (!focused) return;
1160
- if (key.name === "tab" && !key.shift) {
1161
- setInnerFocus((f) => f === "files" ? "diff" : "files");
1162
- return;
1163
- }
1164
- if (innerFocus === "files") {
1165
- if (visibleTree.length > 1 && (key.name === "up" || key.name === "k" && !key.ctrl)) {
1166
- moveCursor(safeCursorIndex - 1);
1167
- return;
1168
- }
1169
- if (visibleTree.length > 1 && (key.name === "down" || key.name === "j" && !key.ctrl)) {
1170
- moveCursor(safeCursorIndex + 1);
1171
- return;
1172
- }
1173
- if (key.name === "return" || key.name === "enter") {
1174
- const node = visibleTree[safeCursorIndex];
1175
- if (node?.type === "folder") toggleFolder(node.path);
1176
- return;
1177
- }
1178
- return;
1179
- }
1180
- if (innerFocus === "diff") {
1181
- const sb = diffScrollRef.current;
1182
- if (!sb) return;
1183
- if (key.ctrl && key.name === "d") {
1184
- sb.scrollBy((sb.viewport?.height ?? 20) / 2);
1185
- } else if (key.ctrl && key.name === "u") {
1186
- sb.scrollBy(-((sb.viewport?.height ?? 20) / 2));
1187
- }
1188
- }
1189
- });
1190
- const statsColumnWidth = useMemo3(() => {
1191
- let max = 0;
1192
- for (const f of files) {
1193
- const a = f.added > 0 ? `+${f.added}`.length : 0;
1194
- const r = f.removed > 0 ? `-${f.removed}`.length : 0;
1195
- const len = a + r + (a > 0 && r > 0 ? 1 : 0);
1196
- if (len > max) max = len;
1197
- }
1198
- return max;
1199
- }, [files]);
1200
- if (files.length === 0) {
1201
- return /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "column", children: [
1202
- /* @__PURE__ */ jsxs4("box", { paddingX: 1, marginBottom: 1, flexDirection: "row", justifyContent: "space-between", flexShrink: 0, height: 1, children: [
1203
- /* @__PURE__ */ jsxs4("text", { children: [
1204
- /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: "Diff" }) }),
1205
- /* @__PURE__ */ jsxs4("span", { fg: "#555555", children: [
1206
- " ",
1207
- "\u2502",
1208
- " "
1209
- ] }),
1210
- /* @__PURE__ */ jsx4("span", { fg: "#cccccc", children: repoName })
1211
- ] }),
1212
- /* @__PURE__ */ jsx4("text", { fg: "#555555", children: "1 file" })
1213
- ] }),
1214
- /* @__PURE__ */ jsx4("scrollbox", { flexGrow: 1, focused, children: /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4(DiffView, { diff }) }) })
1215
- ] });
1216
- }
1217
- const selected = files[safeFileIndex];
1218
- const totalAdded = files.reduce((s, f) => s + f.added, 0);
1219
- const totalRemoved = files.reduce((s, f) => s + f.removed, 0);
1220
- const filesPaneActive = focused === true && innerFocus === "files";
1221
- const diffPaneActive = focused === true && innerFocus === "diff";
1222
- return /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "row", children: [
1223
- /* @__PURE__ */ jsxs4(
1224
- "box",
1225
- {
1226
- width: FILE_PANEL_WIDTH,
1227
- flexDirection: "column",
1228
- backgroundColor: "#0a0a0a",
1229
- flexShrink: 0,
1230
- children: [
1231
- /* @__PURE__ */ jsx4("box", { paddingX: 1, flexShrink: 0, height: 1, children: /* @__PURE__ */ jsxs4("text", { children: [
1232
- /* @__PURE__ */ jsx4("span", { fg: filesPaneActive ? "#66bb6a" : "#333333", children: filesPaneActive ? "\u25B8 " : " " }),
1233
- /* @__PURE__ */ jsx4("span", { fg: filesPaneActive ? "#66bb6a" : "#888888", children: filesPaneActive ? /* @__PURE__ */ jsxs4("strong", { children: [
1234
- files.length,
1235
- " ",
1236
- files.length === 1 ? "file" : "files",
1237
- " changed"
1238
- ] }) : `${files.length} ${files.length === 1 ? "file" : "files"} changed` })
1239
- ] }) }),
1240
- /* @__PURE__ */ jsx4("scrollbox", { ref: fileTreeScrollRef, flexGrow: 1, focused: false, children: visibleTree.map((node, i) => {
1241
- const indent = " ".repeat(node.depth);
1242
- const usable = FILE_PANEL_WIDTH - 2;
1243
- const isCursor = filesPaneActive && i === safeCursorIndex;
1244
- if (node.type === "folder") {
1245
- const isCollapsed = collapsedFolders.has(node.path);
1246
- const chevron = isCollapsed ? "\u25B8" : "\u25BE";
1247
- const overhead2 = indent.length + 2;
1248
- const nameMax2 = Math.max(1, usable - overhead2);
1249
- const displayName2 = truncateName(node.name, nameMax2);
1250
- return /* @__PURE__ */ jsx4(
1251
- "box",
1252
- {
1253
- id: `tree-${node.path}`,
1254
- paddingX: 1,
1255
- height: 1,
1256
- backgroundColor: isCursor ? "#1a1a1a" : void 0,
1257
- onMouseDown: () => {
1258
- setCursorIndex(i);
1259
- toggleFolder(node.path);
1260
- },
1261
- children: /* @__PURE__ */ jsxs4("text", { children: [
1262
- /* @__PURE__ */ jsx4("span", { fg: "#555555", children: indent }),
1263
- /* @__PURE__ */ jsxs4("span", { fg: isCursor ? "#cccccc" : "#888888", children: [
1264
- chevron,
1265
- " ",
1266
- displayName2
1267
- ] })
1268
- ] })
1269
- },
1270
- `folder-${node.path}`
1271
- );
1272
- }
1273
- const isSelectedFile = node.fileIndex === safeFileIndex;
1274
- const added = node.added ?? 0;
1275
- const removed = node.removed ?? 0;
1276
- const addedPart = added > 0 ? `+${added}` : "";
1277
- const removedPart = removed > 0 ? `-${removed}` : "";
1278
- const bubbleColor = added > 0 && removed > 0 ? "#ffaa00" : removed > 0 ? "#ff4444" : "#66bb6a";
1279
- const overhead = indent.length + 2 + statsColumnWidth + 1;
1280
- const nameMax = Math.max(1, usable - overhead);
1281
- const displayName = truncateName(node.name, nameMax);
1282
- const bg = isCursor ? "#0d2b0d" : isSelectedFile ? "#0a1a0a" : void 0;
1283
- return /* @__PURE__ */ jsxs4(
1284
- "box",
1285
- {
1286
- id: `tree-${node.path}`,
1287
- paddingX: 1,
1288
- height: 1,
1289
- backgroundColor: bg,
1290
- onMouseDown: () => selectFile(node.fileIndex, i),
1291
- flexDirection: "row",
1292
- justifyContent: "space-between",
1293
- children: [
1294
- /* @__PURE__ */ jsxs4("text", { children: [
1295
- /* @__PURE__ */ jsx4("span", { fg: "#555555", children: indent }),
1296
- /* @__PURE__ */ jsxs4("span", { fg: bubbleColor, children: [
1297
- "\u25CF",
1298
- " "
1299
- ] }),
1300
- /* @__PURE__ */ jsx4("span", { fg: isSelectedFile || isCursor ? "#66bb6a" : "#cccccc", children: displayName })
1301
- ] }),
1302
- /* @__PURE__ */ jsx4("box", { width: statsColumnWidth, flexShrink: 0, justifyContent: "flex-end", flexDirection: "row", children: /* @__PURE__ */ jsxs4("text", { children: [
1303
- addedPart && /* @__PURE__ */ jsx4("span", { fg: "#66bb6a", children: addedPart }),
1304
- addedPart && removedPart && /* @__PURE__ */ jsx4("span", { fg: "#444444", children: " " }),
1305
- removedPart && /* @__PURE__ */ jsx4("span", { fg: "#ff4444", children: removedPart })
1306
- ] }) })
1307
- ]
1308
- },
1309
- `file-${node.path}`
1310
- );
1311
- }) })
1312
- ]
1313
- }
1314
- ),
1315
- /* @__PURE__ */ jsxs4("box", { flexGrow: 1, flexDirection: "column", children: [
1316
- /* @__PURE__ */ jsxs4("box", { paddingX: 1, flexDirection: "column", flexShrink: 0, children: [
1317
- /* @__PURE__ */ jsxs4("box", { flexDirection: "row", justifyContent: "space-between", height: 1, children: [
1318
- /* @__PURE__ */ jsxs4("text", { children: [
1319
- /* @__PURE__ */ jsx4("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: repoName }) }),
1320
- totalAdded > 0 && /* @__PURE__ */ jsxs4("span", { fg: "#66bb6a", children: [
1321
- " +",
1322
- totalAdded
1323
- ] }),
1324
- totalAdded > 0 && totalRemoved > 0 && /* @__PURE__ */ jsx4("span", { fg: "#444444", children: " " }),
1325
- totalRemoved > 0 && /* @__PURE__ */ jsxs4("span", { fg: "#ff4444", children: [
1326
- "-",
1327
- totalRemoved
1328
- ] })
1329
- ] }),
1330
- /* @__PURE__ */ jsxs4("text", { fg: "#555555", children: [
1331
- safeFileIndex + 1,
1332
- "/",
1333
- files.length
1334
- ] })
1335
- ] }),
1336
- /* @__PURE__ */ jsx4("box", { flexDirection: "row", height: 1, backgroundColor: "#0d0d0d", paddingX: 1, children: /* @__PURE__ */ jsxs4("text", { children: [
1337
- /* @__PURE__ */ jsx4("span", { fg: diffPaneActive ? "#66bb6a" : "#444444", children: diffPaneActive ? "\u25B8 " : " " }),
1338
- (() => {
1339
- const lastSlash = selected.path.lastIndexOf("/");
1340
- const dir = lastSlash >= 0 ? selected.path.slice(0, lastSlash + 1) : "";
1341
- const base = lastSlash >= 0 ? selected.path.slice(lastSlash + 1) : selected.path;
1342
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
1343
- dir && /* @__PURE__ */ jsx4("span", { fg: "#666666", children: dir }),
1344
- /* @__PURE__ */ jsx4("span", { fg: diffPaneActive ? "#66bb6a" : "#ffffff", children: /* @__PURE__ */ jsx4("strong", { children: base }) })
1345
- ] });
1346
- })()
1347
- ] }) })
1348
- ] }),
1349
- /* @__PURE__ */ jsx4("scrollbox", { ref: diffScrollRef, flexGrow: 1, focused: focused && innerFocus === "diff", children: /* @__PURE__ */ jsx4("box", { paddingX: 1, children: /* @__PURE__ */ jsx4(DiffView, { diff: selected.diff, filePath: selected.path }) }) })
1350
- ] })
1351
- ] });
1352
- }
1353
-
1354
- // src/interactive/components/StructuredUserMessage.tsx
1355
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
1356
- function SourceBadge({ source }) {
1357
- const config = SOURCE_CONFIG[source];
1358
- return /* @__PURE__ */ jsx5("text", { children: /* @__PURE__ */ jsx5("span", { fg: "#000000", bg: config.color, children: ` ${config.label} ` }) });
1359
- }
1360
- function MetadataPill({ label, value }) {
1361
- return /* @__PURE__ */ jsx5("text", { children: label ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1362
- /* @__PURE__ */ jsxs5("span", { fg: "#888888", children: [
1363
- label,
1364
- ": "
1365
- ] }),
1366
- /* @__PURE__ */ jsx5("span", { fg: "#cccccc", children: value })
1367
- ] }) : /* @__PURE__ */ jsx5("span", { fg: "#cccccc", children: value }) });
1368
- }
1369
- function MessageBody({ children }) {
1370
- if (!children) return null;
1371
- return /* @__PURE__ */ jsx5("text", { fg: "#ffffff", selectable: true, children });
1372
- }
1373
- function ExpandableSection({ label, children }) {
1374
- const [expanded, setExpanded] = useState4(false);
1375
- if (!children) return null;
1376
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1377
- /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
1378
- /* @__PURE__ */ jsxs5("span", { fg: "#555555", children: [
1379
- expanded ? "\u25BE" : "\u25B8",
1380
- " "
1381
- ] }),
1382
- /* @__PURE__ */ jsx5("span", { fg: "#888888", children: label })
1383
- ] }) }),
1384
- expanded && /* @__PURE__ */ jsx5("box", { paddingLeft: 2, children: /* @__PURE__ */ jsx5("text", { fg: "#888888", selectable: true, children }) })
1385
- ] });
1386
- }
1387
- function ExpandableDiffSection({ label, diffContent, filePath }) {
1388
- const [expanded, setExpanded] = useState4(false);
1389
- if (!diffContent) return null;
1390
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
1391
- /* @__PURE__ */ jsx5("box", { onMouseDown: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("text", { children: [
1392
- /* @__PURE__ */ jsxs5("span", { fg: "#555555", children: [
1393
- expanded ? "\u25BE" : "\u25B8",
1394
- " "
1395
- ] }),
1396
- /* @__PURE__ */ jsx5("span", { fg: "#888888", children: label })
1397
- ] }) }),
1398
- expanded && /* @__PURE__ */ jsx5("box", { paddingLeft: 1, children: /* @__PURE__ */ jsx5(DiffView, { diff: diffContent, filePath }) })
1399
- ] });
1400
- }
1401
- function CIFailureMessage({ data }) {
1402
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1403
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1404
- /* @__PURE__ */ jsx5(SourceBadge, { source: "ci_failure" }),
1405
- /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1406
- /* @__PURE__ */ jsx5(MetadataPill, { label: "Workflow", value: data.workflowName }),
1407
- /* @__PURE__ */ jsx5(MetadataPill, { label: "Status", value: data.conclusion })
1408
- ] }),
1409
- /* @__PURE__ */ jsxs5("text", { fg: "#ffffff", children: [
1410
- "Workflow ",
1411
- /* @__PURE__ */ jsx5("span", { fg: "#ff4444", children: data.conclusionText }),
1412
- " on PR #" + data.prNumber
1413
- ] }),
1414
- data.workflowFile && /* @__PURE__ */ jsx5(MetadataPill, { label: "File", value: data.workflowFile }),
1415
- data.runUrl && /* @__PURE__ */ jsx5(MetadataPill, { label: "Run", value: data.runUrl })
1416
- ] });
1417
- }
1418
- function LinearIssueMessage({ data }) {
1419
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1420
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1421
- /* @__PURE__ */ jsx5(SourceBadge, { source: "linear_issue" }),
1422
- /* @__PURE__ */ jsx5(MetadataPill, { label: "", value: data.identifier })
1423
- ] }),
1424
- /* @__PURE__ */ jsx5("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx5("strong", { children: data.title }) }),
1425
- data.description && data.description !== "No description provided." && /* @__PURE__ */ jsx5(MessageBody, { children: data.description }),
1426
- /* @__PURE__ */ jsx5(ExpandableSection, { label: "Additional context", children: data.additionalContext }),
1427
- data.parentIssue && /* @__PURE__ */ jsx5(ExpandableSection, { label: `Parent: ${data.parentIssue.identifier} - ${data.parentIssue.title}`, children: data.parentIssue.description })
1428
- ] });
1429
- }
1430
- function GitHubIssueNewMessage({ data }) {
1431
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1432
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1433
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_issue_new" }),
1434
- /* @__PURE__ */ jsx5(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
1435
- data.triggeringUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.triggeringUser}` })
1436
- ] }),
1437
- /* @__PURE__ */ jsx5("text", { fg: "#ffffff", children: /* @__PURE__ */ jsx5("strong", { children: data.issueTitle }) }),
1438
- data.triggeringComment && /* @__PURE__ */ jsx5(MessageBody, { children: data.triggeringComment }),
1439
- /* @__PURE__ */ jsx5(ExpandableSection, { label: "Issue description", children: data.issueDescription })
1440
- ] });
1441
- }
1442
- function GitHubIssueExistingMessage({ data }) {
1443
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1444
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1445
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_issue_existing" }),
1446
- /* @__PURE__ */ jsx5(MetadataPill, { label: "Issue", value: `#${data.issueNumber}` }),
1447
- data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` })
1448
- ] }),
1449
- /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
1450
- ] });
1451
- }
1452
- function GitHubPRNewMessage({ data }) {
1453
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1454
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1455
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_new" }),
1456
- /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1457
- data.mentioningUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.mentioningUser}` })
1458
- ] }),
1459
- /* @__PURE__ */ jsx5(MessageBody, { children: data.contextMessage })
1460
- ] });
1461
- }
1462
- function GitHubPRCodeReviewMessage({ data }) {
1463
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1464
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1465
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_review" }),
1466
- /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1467
- data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` }),
1468
- data.filePath && /* @__PURE__ */ jsx5(MetadataPill, { label: "File", value: data.filePath })
1469
- ] }),
1470
- /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody }),
1471
- /* @__PURE__ */ jsx5(ExpandableDiffSection, { label: "Diff context", diffContent: data.diffHunk, filePath: data.filePath })
1472
- ] });
1473
- }
1474
- function GitHubPRReviewMessage({ data }) {
1475
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1476
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1477
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_pr_review" }),
1478
- /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1479
- data.reviewUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.reviewUser}` })
1480
- ] }),
1481
- /* @__PURE__ */ jsxs5("text", { fg: "#888888", children: [
1482
- "@" + data.reviewUser + " ",
1483
- /* @__PURE__ */ jsx5("span", { fg: "#ffffff", children: data.reviewAction })
1484
- ] }),
1485
- data.commentBody && /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
1486
- ] });
1487
- }
1488
- function GitHubPRGeneralMessage({ data }) {
1489
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1490
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1491
- /* @__PURE__ */ jsx5(SourceBadge, { source: "github_pr_existing_general" }),
1492
- /* @__PURE__ */ jsx5(MetadataPill, { label: "PR", value: `#${data.prNumber}` }),
1493
- data.commentUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.commentUser}` })
1494
- ] }),
1495
- /* @__PURE__ */ jsx5(MessageBody, { children: data.commentBody })
1496
- ] });
1497
- }
1498
- function SlackTaskMessage({ data }) {
1499
- const hasDistinctContext = data.threadContext && data.threadContext !== `@${data.latestMessageUser}: ${data.latestMessageText}`;
1500
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1501
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1502
- /* @__PURE__ */ jsx5(SourceBadge, { source: "slack_task" }),
1503
- data.workspaceTarget && /* @__PURE__ */ jsx5(MetadataPill, { label: "Target", value: data.workspaceTarget }),
1504
- data.latestMessageUser && /* @__PURE__ */ jsx5(MetadataPill, { label: "From", value: `@${data.latestMessageUser}` })
1505
- ] }),
1506
- /* @__PURE__ */ jsx5(MessageBody, { children: data.latestMessageText }),
1507
- hasDistinctContext && /* @__PURE__ */ jsx5(ExpandableSection, { label: "Thread context", children: data.threadContext })
1508
- ] });
1509
- }
1510
- function InlineDiffCommentsMessage({ data }) {
1511
- return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", gap: 0, children: [
1512
- /* @__PURE__ */ jsxs5("box", { flexDirection: "row", gap: 1, children: [
1513
- /* @__PURE__ */ jsx5(SourceBadge, { source: "inline_diff_comments" }),
1514
- /* @__PURE__ */ jsx5(MetadataPill, { label: "Comments", value: `${data.comments.length}` }),
1515
- data.comments[0] && /* @__PURE__ */ jsx5(MetadataPill, { label: "File", value: data.comments[0].location })
1516
- ] }),
1517
- data.leadingPlanQuote ? /* @__PURE__ */ jsx5(MessageBody, { children: data.leadingPlanQuote.blocks.map((b) => `> ${b.quotedText}
1518
- ${b.replyText}`).join("\n\n") }) : data.leadingText && /* @__PURE__ */ jsx5(MessageBody, { children: data.leadingText }),
1519
- data.comments.map((comment, index) => /* @__PURE__ */ jsxs5("box", { flexDirection: "column", paddingLeft: 1, children: [
1520
- /* @__PURE__ */ jsxs5("text", { fg: "#888888", children: [
1521
- comment.location,
1522
- " (",
1523
- comment.side,
1524
- ")"
1525
- ] }),
1526
- /* @__PURE__ */ jsx5(MessageBody, { children: comment.body })
1527
- ] }, `${comment.location}-${index}`))
1528
- ] });
1529
- }
1530
- function StructuredUserMessage({ parsed }) {
1531
- switch (parsed.source) {
1532
- case "ci_failure":
1533
- return /* @__PURE__ */ jsx5(CIFailureMessage, { data: parsed });
1534
- case "linear_issue":
1535
- return /* @__PURE__ */ jsx5(LinearIssueMessage, { data: parsed });
1536
- case "github_issue_new":
1537
- return /* @__PURE__ */ jsx5(GitHubIssueNewMessage, { data: parsed });
1538
- case "github_issue_existing":
1539
- return /* @__PURE__ */ jsx5(GitHubIssueExistingMessage, { data: parsed });
1540
- case "github_pr_new":
1541
- return /* @__PURE__ */ jsx5(GitHubPRNewMessage, { data: parsed });
1542
- case "github_pr_existing_review":
1543
- return /* @__PURE__ */ jsx5(GitHubPRCodeReviewMessage, { data: parsed });
1544
- case "github_pr_existing_pr_review":
1545
- return /* @__PURE__ */ jsx5(GitHubPRReviewMessage, { data: parsed });
1546
- case "github_pr_existing_general":
1547
- return /* @__PURE__ */ jsx5(GitHubPRGeneralMessage, { data: parsed });
1548
- case "slack_task":
1549
- return /* @__PURE__ */ jsx5(SlackTaskMessage, { data: parsed });
1550
- case "inline_diff_comments":
1551
- return /* @__PURE__ */ jsx5(InlineDiffCommentsMessage, { data: parsed });
1552
- case "automation_triggered":
1553
- case "plan_quote":
1554
- return /* @__PURE__ */ jsx5("text", { fg: "#cccccc", selectable: true, children: parsed.source === "plan_quote" ? parsed.blocks.map((b) => `> ${b.quotedText}
1555
- ${b.replyText}`).join("\n\n") : `Automation: ${parsed.automationName} (${parsed.triggerType})
1556
- ${parsed.userPrompt}` });
1557
- default: {
1558
- const _exhaustive = parsed;
1559
- throw new Error(`Unhandled prompt source: ${_exhaustive.source}`);
1560
- }
1561
- }
1562
- }
1563
-
1564
- // src/interactive/components/ChatMessage.tsx
1565
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
1566
- var AGENT_LABELS = {
1567
- claude: "Claude",
1568
- codex: "Codex",
1569
- relay: "Relay"
1570
- };
1571
- function truncate2(text, maxLen) {
1572
- return text.length > maxLen ? text.slice(0, maxLen - 1) + "\u2026" : text;
1573
- }
1574
- function StatusIcon({ status }) {
1575
- if (status === "completed") return /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1576
- " ",
1577
- "\u2713"
1578
- ] });
1579
- if (status === "failed") return /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1580
- " ",
1581
- "\u2717"
1582
- ] });
1583
- if (status === "in_progress") return /* @__PURE__ */ jsxs6("span", { fg: "#ffaa00", children: [
1584
- " ",
1585
- "\u2026"
1586
- ] });
1587
- return null;
1588
- }
1589
- function ActionLine({
1590
- label,
1591
- arg,
1592
- status,
1593
- expandable,
1594
- onToggle,
1595
- expanded
1596
- }) {
1597
- return /* @__PURE__ */ jsxs6(
1598
- "box",
1599
- {
1600
- paddingX: 1,
1601
- flexDirection: "row",
1602
- justifyContent: "space-between",
1603
- onMouseDown: onToggle,
1604
- children: [
1605
- /* @__PURE__ */ jsxs6("text", { children: [
1606
- /* @__PURE__ */ jsx6("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx6("strong", { children: label }) }),
1607
- /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1608
- " ",
1609
- arg.replace(/\n/g, " ").replace(/\s+/g, " ")
1610
- ] }),
1611
- /* @__PURE__ */ jsx6(StatusIcon, { status })
1612
- ] }),
1613
- expandable && /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
1614
- ]
1615
- }
1616
- );
1617
- }
1618
- function ExpandableAction({
1619
- label,
1620
- arg,
1621
- status,
1622
- children
1623
- }) {
1624
- const [expanded, setExpanded] = useState5(false);
1625
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1626
- /* @__PURE__ */ jsx6(
1627
- ActionLine,
1628
- {
1629
- label,
1630
- arg,
1631
- status,
1632
- expandable: true,
1633
- expanded,
1634
- onToggle: () => setExpanded((e) => !e)
1635
- }
1636
- ),
1637
- expanded && children
1638
- ] });
1639
- }
1640
- var CHANGE_STYLE = {
1641
- add: { color: "#66bb6a", sign: "+" },
1642
- delete: { color: "#ff4444", sign: "-" }
1643
- };
1644
- var DEFAULT_CHANGE_STYLE = { color: "#ffaa00", sign: "~" };
1645
- function getChangeStyle(action) {
1646
- return CHANGE_STYLE[action] ?? DEFAULT_CHANGE_STYLE;
1647
- }
1648
- function PatchOperation({ op, defaultExpanded = false }) {
1649
- const [expanded, setExpanded] = useState5(defaultExpanded);
1650
- const { color, sign } = getChangeStyle(op.action);
1651
- const stats = useMemo4(() => op.diff ? countDiffStats(op.diff) : null, [op.diff]);
1652
- if (!op.diff) {
1653
- return /* @__PURE__ */ jsxs6("text", { children: [
1654
- /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1655
- /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1656
- " ",
1657
- op.path
1658
- ] })
1659
- ] });
1660
- }
1661
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
1662
- /* @__PURE__ */ jsxs6("box", { onMouseDown: () => setExpanded((e) => !e), flexDirection: "row", justifyContent: "space-between", children: [
1663
- /* @__PURE__ */ jsxs6("text", { children: [
1664
- /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1665
- /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1666
- " ",
1667
- op.path
1668
- ] }),
1669
- stats && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1670
- /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " " }),
1671
- stats.removed > 0 && /* @__PURE__ */ jsxs6("span", { fg: "#ff4444", children: [
1672
- "-",
1673
- stats.removed
1674
- ] }),
1675
- stats.removed > 0 && stats.added > 0 && /* @__PURE__ */ jsx6("span", { fg: "#444444", children: " " }),
1676
- stats.added > 0 && /* @__PURE__ */ jsxs6("span", { fg: "#66bb6a", children: [
1677
- "+",
1678
- stats.added
1679
- ] })
1680
- ] })
1681
- ] }),
1682
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#555555", children: expanded ? "\u25BE" : "\u25B8" }) })
1683
- ] }),
1684
- expanded && /* @__PURE__ */ jsx6("box", { paddingLeft: 1, children: /* @__PURE__ */ jsx6(DiffView, { diff: op.diff, filePath: op.path }) })
1685
- ] });
1686
- }
1687
- function isStructuredPrompt(p) {
1688
- return p.source !== "raw";
1689
- }
1690
- function UserMessageContent({ content }) {
1691
- const parsed = useMemo4(() => parseUserMessage(content), [content]);
1692
- return /* @__PURE__ */ jsx6(
1693
- "box",
1694
- {
1695
- flexDirection: "column",
1696
- backgroundColor: "#151515",
1697
- paddingX: 2,
1698
- paddingY: 1,
1699
- marginX: 1,
1700
- children: isStructuredPrompt(parsed) ? /* @__PURE__ */ jsx6(StructuredUserMessage, { parsed }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
1701
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx6("strong", { children: "You" }) }) }),
1702
- /* @__PURE__ */ jsx6("text", { fg: "#ffffff", selectable: true, children: content })
1703
- ] })
1704
- }
1705
- );
1706
- }
1707
- function ChatMessage({ message, provider }) {
1708
- switch (message.type) {
1709
- case "user": {
1710
- if (message.content === "Request interrupted") {
1711
- return /* @__PURE__ */ jsx6("box", { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx6("text", { fg: "#555555", children: "--- Request interrupted ---" }) });
1712
- }
1713
- return /* @__PURE__ */ jsx6(UserMessageContent, { content: message.content });
1714
- }
1715
- case "agent":
1716
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingX: 1, children: [
1717
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx6("strong", { children: AGENT_LABELS[provider] }) }) }),
1718
- /* @__PURE__ */ jsx6(
1719
- "markdown",
1720
- {
1721
- content: message.content,
1722
- streaming: true,
1723
- syntaxStyle: getSyntaxStyle(),
1724
- conceal: true,
1725
- fg: "#ffffff",
1726
- bg: "#000000"
1727
- }
1728
- )
1729
- ] });
1730
- case "reasoning":
1731
- return /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6(SpinnerLabel, { color: "#555555", label: "thinking" }) });
1732
- case "command": {
1733
- const cmdArg = truncate2(message.command, 60);
1734
- const exitSuffix = message.exitCode !== void 0 && message.exitCode !== 0 ? ` (exit ${message.exitCode})` : "";
1735
- if (!message.output) {
1736
- return /* @__PURE__ */ jsx6(ActionLine, { label: "Command", arg: cmdArg + exitSuffix, status: message.status });
1737
- }
1738
- return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Command", arg: cmdArg + exitSuffix, status: message.status, children: /* @__PURE__ */ jsx6("box", { paddingLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: message.output.length > 500 ? message.output.slice(0, 500) + "\n\u2026" : message.output }) }) });
1739
- }
1740
- case "file_change": {
1741
- const count = message.changes.length;
1742
- return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Files", arg: `${count}`, status: message.status, children: /* @__PURE__ */ jsx6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.changes.map((change, i) => {
1743
- const { color, sign } = getChangeStyle(change.kind);
1744
- return /* @__PURE__ */ jsxs6("text", { children: [
1745
- /* @__PURE__ */ jsx6("span", { fg: color, children: sign }),
1746
- /* @__PURE__ */ jsxs6("span", { fg: "#cccccc", children: [
1747
- " ",
1748
- change.path
1749
- ] })
1750
- ] }, i);
1751
- }) }) });
1752
- }
1753
- case "tool_call": {
1754
- const toolArg = truncate2(message.tool, 50);
1755
- if (message.output || message.input) {
1756
- return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Tool", arg: toolArg, status: message.status, children: /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: [
1757
- message.input && /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: typeof message.input === "string" ? truncate2(message.input, 300) : truncate2(JSON.stringify(message.input, null, 2), 300) }),
1758
- message.output && /* @__PURE__ */ jsx6("text", { fg: "#888888", selectable: true, children: truncate2(message.output, 300) })
1759
- ] }) });
1760
- }
1761
- return /* @__PURE__ */ jsx6(ActionLine, { label: "Tool", arg: toolArg, status: message.status });
1762
- }
1763
- case "web_search":
1764
- return /* @__PURE__ */ jsx6(ActionLine, { label: "Search", arg: truncate2(message.query, 50), status: message.status });
1765
- case "todo_list": {
1766
- const completed = message.items.filter((i) => i.completed).length;
1767
- const total = message.items.length;
1768
- return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", paddingX: 1, children: [
1769
- /* @__PURE__ */ jsx6("text", { children: /* @__PURE__ */ jsx6("span", { fg: "#888888", children: /* @__PURE__ */ jsxs6("strong", { children: [
1770
- "Plan (",
1771
- completed,
1772
- "/",
1773
- total,
1774
- ")"
1775
- ] }) }) }),
1776
- message.items.map((item, i) => /* @__PURE__ */ jsxs6("text", { children: [
1777
- /* @__PURE__ */ jsx6("span", { fg: item.completed ? "#66bb6a" : "#555555", children: item.completed ? " \u2611" : " \u2610" }),
1778
- /* @__PURE__ */ jsxs6("span", { fg: "#ffffff", children: [
1779
- " ",
1780
- item.text
1781
- ] })
1782
- ] }, i))
1783
- ] });
1784
- }
1785
- case "subagent":
1786
- return /* @__PURE__ */ jsx6(ActionLine, { label: "Agent", arg: truncate2(message.description, 50), status: message.status });
1787
- case "error":
1788
- return /* @__PURE__ */ jsx6("box", { paddingX: 1, children: /* @__PURE__ */ jsx6("text", { fg: "#ff4444", selectable: true, children: message.message }) });
1789
- case "skill":
1790
- return /* @__PURE__ */ jsx6(ActionLine, { label: "Skill", arg: message.skillName + (message.args ? ` ${message.args}` : ""), status: message.status });
1791
- case "patch": {
1792
- const opCount = message.operations.length;
1793
- const autoExpand = opCount === 1 && !!message.operations[0].diff;
1794
- return /* @__PURE__ */ jsx6(ExpandableAction, { label: "Patch", arg: `${opCount}`, children: /* @__PURE__ */ jsx6("box", { flexDirection: "column", paddingLeft: 2, paddingX: 1, children: message.operations.map((op, i) => /* @__PURE__ */ jsx6(PatchOperation, { op, defaultExpanded: autoExpand }, i)) }) });
1795
- }
1796
- default:
1797
- return null;
1798
- }
1799
- }
1800
-
1801
- // src/interactive/components/ChatArea.tsx
1802
- import { jsx as jsx7, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
1803
- var textareaKeyBindings = [
1804
- { name: "return", action: "submit" },
1805
- { name: "return", shift: true, action: "newline" },
1806
- { name: "return", ctrl: true, action: "newline" },
1807
- { name: "return", meta: true, action: "newline" }
1808
- ];
1809
- var MODE_COLORS = {
1810
- build: { border: "#66bb6a", text: "#66bb6a" },
1811
- plan: { border: "#d97706", text: "#d97706" }
1812
- };
1813
- function ChatArea({
1814
- chats,
1815
- selectedChatId,
1816
- displayMessages,
1817
- onSelectChat,
1818
- onSendMessage,
1819
- onFocus,
1820
- focusPanel,
1821
- taskMode,
1822
- isProcessing,
1823
- loading,
1824
- agentAvailable,
1825
- agentLabel
1826
- }) {
1827
- const textareaRef = useRef2(null);
1828
- const scrollboxRef = useRef2(null);
1829
- const isAtBottomRef = useRef2(true);
1830
- const onSendMessageRef = useRef2(onSendMessage);
1831
- onSendMessageRef.current = onSendMessage;
1832
- const inputFocused = focusPanel === "chat-input";
1833
- const tabsFocused = focusPanel === "chat-tabs";
1834
- const historyFocused = focusPanel === "chat-history";
1835
- const anyFocused = inputFocused || tabsFocused || historyFocused;
1836
- const modeColors = MODE_COLORS[taskMode];
1837
- useEffect2(() => {
1838
- const scrollbox = scrollboxRef.current;
1839
- if (!scrollbox) return;
1840
- let lastScrollHeight = 0;
1841
- const tick = () => {
1842
- const viewportHeight = scrollbox.viewport?.height ?? 0;
1843
- const atBottom = scrollbox.scrollTop + viewportHeight >= scrollbox.scrollHeight - 3;
1844
- if (atBottom) {
1845
- isAtBottomRef.current = true;
1846
- } else if (scrollbox.scrollHeight === lastScrollHeight) {
1847
- isAtBottomRef.current = false;
1848
- }
1849
- if (isAtBottomRef.current && scrollbox.scrollHeight > lastScrollHeight) {
1850
- scrollbox.scrollTo(scrollbox.scrollHeight);
1851
- }
1852
- lastScrollHeight = scrollbox.scrollHeight;
1853
- };
1854
- const interval = setInterval(tick, 100);
1855
- return () => clearInterval(interval);
1856
- }, []);
1857
- useEffect2(() => {
1858
- const textarea = textareaRef.current;
1859
- if (!textarea) return;
1860
- textarea.onSubmit = () => {
1861
- const message = textarea.plainText.trim();
1862
- if (message) {
1863
- isAtBottomRef.current = true;
1864
- onSendMessageRef.current(message);
1865
- textarea.setText("");
1866
- }
1867
- };
1868
- return () => {
1869
- textarea.onSubmit = void 0;
1870
- };
1871
- }, []);
1872
- useKeyboard3((key) => {
1873
- if (!tabsFocused || chats.length <= 1 || !selectedChatId) return;
1874
- if (key.name === "left" || key.name === "right" || key.name === "tab" && !key.shift) {
1875
- const currentIdx = chats.findIndex((c) => c.id === selectedChatId);
1876
- if (currentIdx >= 0) {
1877
- const nextIdx = key.name === "left" ? (currentIdx - 1 + chats.length) % chats.length : (currentIdx + 1) % chats.length;
1878
- onSelectChat(chats[nextIdx].id);
1879
- }
1880
- }
1881
- });
1882
- return /* @__PURE__ */ jsxs7(
1883
- "box",
1884
- {
1885
- flexGrow: 1,
1886
- border: true,
1887
- borderStyle: "rounded",
1888
- borderColor: anyFocused ? "#66bb6a" : "#333333",
1889
- title: "Chat",
1890
- titleAlignment: "center",
1891
- flexDirection: "column",
1892
- backgroundColor: "#000000",
1893
- children: [
1894
- chats.length > 1 && /* @__PURE__ */ jsx7(
1895
- "box",
1896
- {
1897
- height: 3,
1898
- paddingX: 1,
1899
- marginX: 1,
1900
- border: true,
1901
- borderColor: tabsFocused ? "#66bb6a" : "#333333",
1902
- backgroundColor: "#000000",
1903
- flexDirection: "row",
1904
- gap: 1,
1905
- alignItems: "center",
1906
- onMouseDown: () => onFocus("chat-tabs"),
1907
- children: chats.map((chat) => {
1908
- const isSelected = chat.id === selectedChatId;
1909
- const label = `${chat.title}${chat.processing ? " ..." : ""}`;
1910
- return /* @__PURE__ */ jsx7(
1911
- "box",
1912
- {
1913
- backgroundColor: isSelected ? "#1a1a1a" : void 0,
1914
- paddingX: 1,
1915
- onMouseDown: () => {
1916
- onSelectChat(chat.id);
1917
- onFocus("chat-tabs");
1918
- },
1919
- children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: isSelected ? modeColors.text : "#666666", children: label }) })
1920
- },
1921
- chat.id
1922
- );
1923
- })
1924
- }
1925
- ),
1926
- /* @__PURE__ */ jsx7(
1927
- "scrollbox",
1928
- {
1929
- ref: scrollboxRef,
1930
- flexGrow: 1,
1931
- focused: historyFocused,
1932
- paddingX: 1,
1933
- paddingY: 1,
1934
- onMouseDown: () => onFocus("chat-history"),
1935
- children: loading ? /* @__PURE__ */ jsx7("box", { paddingX: 1, children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "Loading messages..." }) }) : displayMessages.length === 0 ? /* @__PURE__ */ jsx7("box", { paddingX: 1, children: /* @__PURE__ */ jsx7("text", { fg: "#666666", children: "No messages yet. Send a message to start chatting." }) }) : (() => {
1936
- const activeChat = chats.find((c) => c.id === selectedChatId);
1937
- const provider = activeChat?.provider ?? "claude";
1938
- const primaryTypes = /* @__PURE__ */ new Set(["user", "agent", "todo_list"]);
1939
- return displayMessages.map((msg, i) => {
1940
- const prev = i > 0 ? displayMessages[i - 1] : null;
1941
- const needsSpacing = prev && (primaryTypes.has(msg.type) || primaryTypes.has(prev.type));
1942
- return /* @__PURE__ */ jsxs7("box", { flexDirection: "column", children: [
1943
- needsSpacing && /* @__PURE__ */ jsx7("box", { height: 1 }),
1944
- /* @__PURE__ */ jsx7(ChatMessage, { message: msg, provider })
1945
- ] }, msg.id || `msg-${i}`);
1946
- });
1947
- })()
1948
- }
1949
- ),
1950
- /* @__PURE__ */ jsxs7(
1951
- "box",
1952
- {
1953
- flexDirection: "column",
1954
- flexShrink: 0,
1955
- border: true,
1956
- borderColor: inputFocused ? modeColors.border : "#333333",
1957
- backgroundColor: "#111111",
1958
- onMouseDown: () => onFocus("chat-input"),
1959
- children: [
1960
- /* @__PURE__ */ jsx7("box", { height: 6, paddingX: 1, children: /* @__PURE__ */ jsx7(
1961
- "textarea",
1962
- {
1963
- ref: textareaRef,
1964
- keyBindings: textareaKeyBindings,
1965
- placeholder: "Type a message... (Enter to send, Shift+Enter for newline)",
1966
- focused: inputFocused,
1967
- backgroundColor: "#111111",
1968
- textColor: "#ffffff",
1969
- focusedBackgroundColor: "#111111",
1970
- placeholderColor: "#555555",
1971
- width: "100%",
1972
- height: 5
1973
- }
1974
- ) }),
1975
- !agentAvailable && /* @__PURE__ */ jsx7("box", { paddingX: 1, height: 1, children: /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsxs7("span", { fg: "#d97706", children: [
1976
- agentLabel,
1977
- " is not connected. Run `replicas ",
1978
- agentLabel === "Codex" ? "codex-auth" : "claude-auth",
1979
- "` to connect."
1980
- ] }) }) }),
1981
- /* @__PURE__ */ jsxs7(
1982
- "box",
1983
- {
1984
- height: 1,
1985
- paddingX: 1,
1986
- flexDirection: "row",
1987
- justifyContent: "space-between",
1988
- children: [
1989
- /* @__PURE__ */ jsx7("text", { children: /* @__PURE__ */ jsx7("span", { fg: modeColors.text, children: taskMode === "build" ? "Build" : "Plan" }) }),
1990
- isProcessing && /* @__PURE__ */ jsx7(SpinnerLabel, { color: "#ffaa00", label: "thinking" })
1991
- ]
1992
- }
1993
- )
1994
- ]
1995
- }
1996
- )
1997
- ]
1998
- }
1999
- );
2000
- }
2001
-
2002
- // src/interactive/components/WorkspaceInfo.tsx
2003
- import React6, { useState as useState6, useMemo as useMemo5, useCallback as useCallback5, useRef as useRef3 } from "react";
2004
- import open from "open";
2005
- import { useKeyboard as useKeyboard4 } from "@opentui/react";
2006
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
2007
- var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://tryreplicas.com";
2008
- function buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId) {
2009
- const items = [];
2010
- const hasAnyPr = status.repoStatuses?.some((repo) => repo.prUrls.length > 0);
2011
- const githubConfigured = status.environmentDetails?.githubAccessConfigured;
2012
- if (workspaceId && status.status === "active" && !hasAnyPr && githubConfigured) {
2013
- items.push({ type: "createPr" });
2014
- }
2015
- if (workspaceId) {
2016
- items.push({ type: "dashboard", workspaceId });
2017
- }
2018
- if (workspaceId && status.status === "sleeping" && wakingWorkspaceId !== workspaceId) {
2019
- items.push({ type: "wake", workspaceId });
2020
- }
2021
- const repoDiffs = [];
2022
- if (status.repoStatuses) {
2023
- for (const repo of status.repoStatuses) {
2024
- if (repo.gitDiff?.fullDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0)) {
2025
- repoDiffs.push({ type: "diff", repoName: repo.name, diff: repo.gitDiff.fullDiff, added: repo.gitDiff.added, removed: repo.gitDiff.removed });
2026
- }
2027
- }
2028
- }
2029
- items.push({ type: "view-chat" });
2030
- if (repoDiffs.length > 0) {
2031
- items.push({ type: "view-diff" });
2032
- items.push(...repoDiffs);
2033
- }
2034
- for (const preview of previews) {
2035
- if (preview.publicUrl) {
2036
- items.push({ type: "preview", url: preview.publicUrl, port: preview.port });
2037
- }
2038
- }
2039
- if (status.repoStatuses) {
2040
- for (const repo of status.repoStatuses) {
2041
- for (const prUrl of repo.prUrls) {
2042
- items.push({ type: "pr", url: prUrl, repoName: repo.name });
2043
- }
2044
- }
2045
- }
2046
- return items;
2047
- }
2048
- function getItemLabel(item) {
2049
- switch (item.type) {
2050
- case "dashboard":
2051
- return "\u2197 Open in Dashboard";
2052
- case "wake":
2053
- return "\u25B6 Wake";
2054
- case "preview":
2055
- return `\u2197 Preview :${item.port}`;
2056
- case "pr": {
2057
- const prNumber = extractPrNumber(item.url);
2058
- const suffix = prNumber ? ` #${prNumber}` : "";
2059
- return `\u2197 View PR (${item.repoName})${suffix}`;
2060
- }
2061
- case "diff":
2062
- return `\u25B8 Diff +${item.added} -${item.removed}`;
2063
- case "view-chat":
2064
- return "1 Chat";
2065
- case "view-diff":
2066
- return "2 Diff";
2067
- case "createPr":
2068
- return "+ Create PR";
2069
- }
2070
- }
2071
- function getItemId(item) {
2072
- switch (item.type) {
2073
- case "dashboard":
2074
- return "info-dashboard";
2075
- case "wake":
2076
- return "info-wake";
2077
- case "preview":
2078
- return `info-preview-${item.port}`;
2079
- case "pr": {
2080
- const prNumber = extractPrNumber(item.url);
2081
- return `info-pr-${item.repoName}-${prNumber ?? item.url}`;
2082
- }
2083
- case "diff":
2084
- return `info-diff-${item.repoName}`;
2085
- case "view-chat":
2086
- return "info-view-chat";
2087
- case "view-diff":
2088
- return "info-view-diff";
2089
- case "createPr":
2090
- return "info-create-pr";
2091
- }
2092
- }
2093
- var AUTH_METHOD_LABELS = {
2094
- oauth: "OAuth",
2095
- api_key: "API Key",
2096
- bedrock: "Bedrock",
2097
- none: null
2098
- };
2099
- function StatusDot({ status }) {
2100
- if (status === true || status === "yes") {
2101
- return /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: "\u2713" });
2102
- }
2103
- if (status === false || status === "no") {
2104
- return /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" });
2105
- }
2106
- return /* @__PURE__ */ jsx8("span", { fg: "#555555", children: "-" });
2107
- }
2108
- function SectionLabel({ title }) {
2109
- return /* @__PURE__ */ jsx8("box", { backgroundColor: "#151515", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#666666", children: /* @__PURE__ */ jsx8("strong", { children: title.toUpperCase() }) }) }) });
2110
- }
2111
- function Section({ title, children }) {
2112
- return /* @__PURE__ */ jsxs8("box", { flexDirection: "column", marginBottom: 1, children: [
2113
- /* @__PURE__ */ jsx8(SectionLabel, { title }),
2114
- /* @__PURE__ */ jsx8("box", { flexDirection: "column", backgroundColor: "#0a0a0a", children })
2115
- ] });
2116
- }
2117
- function CardItem({ label, status }) {
2118
- return /* @__PURE__ */ jsxs8(
2119
- "box",
2120
- {
2121
- flexDirection: "row",
2122
- justifyContent: "space-between",
2123
- paddingX: 1,
2124
- backgroundColor: "#111111",
2125
- marginBottom: 0,
2126
- children: [
2127
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: label }) }),
2128
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status }) })
2129
- ]
2130
- }
2131
- );
2132
- }
2133
- function DetailList({
2134
- expected,
2135
- actual
2136
- }) {
2137
- const uniqueExpected = Array.from(new Set(expected)).sort((a, b) => a.localeCompare(b));
2138
- const actualSet = new Set(actual);
2139
- const rows = uniqueExpected.length > 0 ? uniqueExpected : Array.from(actualSet).sort((a, b) => a.localeCompare(b));
2140
- if (rows.length === 0) return null;
2141
- return /* @__PURE__ */ jsx8(Fragment4, { children: rows.map((item, i) => /* @__PURE__ */ jsx8(CardItem, { label: item, status: actualSet.has(item) }, i)) });
2142
- }
2143
- function InteractiveRow({ id, label, highlighted, disabled, onClick }) {
2144
- if (disabled) {
2145
- return /* @__PURE__ */ jsx8("box", { id, paddingX: 1, backgroundColor: "#111111", children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#444444", children: label }) }) });
2146
- }
2147
- return /* @__PURE__ */ jsx8(
2148
- "box",
2149
- {
2150
- id,
2151
- paddingX: 1,
2152
- backgroundColor: highlighted ? "#1a2a1a" : "#111111",
2153
- onMouseDown: onClick,
2154
- children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: highlighted ? "#66bb6a" : "#7dcfff", children: label }) })
2155
- }
2156
- );
2157
- }
2158
- function ViewModeRow({
2159
- id,
2160
- label,
2161
- active,
2162
- highlighted,
2163
- disabled,
2164
- onClick
2165
- }) {
2166
- const bg = highlighted ? "#1a2a1a" : "#111111";
2167
- const fg = disabled ? "#444444" : active ? "#66bb6a" : "#cccccc";
2168
- const marker = active ? "\u25B8" : " ";
2169
- return /* @__PURE__ */ jsx8(
2170
- "box",
2171
- {
2172
- id,
2173
- paddingX: 1,
2174
- backgroundColor: bg,
2175
- onMouseDown: disabled ? void 0 : onClick,
2176
- children: /* @__PURE__ */ jsxs8("text", { children: [
2177
- /* @__PURE__ */ jsxs8("span", { fg: active ? "#66bb6a" : "#555555", children: [
2178
- marker,
2179
- " "
2180
- ] }),
2181
- active ? /* @__PURE__ */ jsx8("span", { fg, children: /* @__PURE__ */ jsx8("strong", { children: label }) }) : /* @__PURE__ */ jsx8("span", { fg, children: label })
2182
- ] })
2183
- }
2184
- );
2185
- }
2186
- function WorkspaceInfo({ status, workspaceName, workspaceId, focused, loading, agentAvailability, previews, onWakeWorkspace, onViewDiff, wakingWorkspaceId, viewMode, viewingDiffRepoName, onSelectChatMode, onSelectDiffMode, onCreatePr, isPlanMode }) {
2187
- const borderColor = focused ? "#66bb6a" : "#333333";
2188
- const [cursorIndex, setCursorIndex] = useState6(0);
2189
- const interactiveItems = useMemo5(() => {
2190
- if (!status || !workspaceName) return [];
2191
- return buildInteractiveItems(workspaceId, status, previews, wakingWorkspaceId);
2192
- }, [workspaceId, workspaceName, status, previews, wakingWorkspaceId]);
2193
- const diffItems = useMemo5(
2194
- () => interactiveItems.filter((item) => item.type === "diff"),
2195
- [interactiveItems]
2196
- );
2197
- const hasAnyDiff = diffItems.length > 0;
2198
- const safeCursor = interactiveItems.length > 0 ? Math.min(cursorIndex, interactiveItems.length - 1) : 0;
2199
- const scrollboxRef = useRef3(null);
2200
- const moveCursor = useCallback5(
2201
- (next) => {
2202
- const len = interactiveItems.length;
2203
- if (len === 0) return;
2204
- const wrapped = (next % len + len) % len;
2205
- setCursorIndex(wrapped);
2206
- const item = interactiveItems[wrapped];
2207
- if (item) scrollboxRef.current?.scrollChildIntoView(getItemId(item));
2208
- },
2209
- [interactiveItems]
2210
- );
2211
- const handleAction = useCallback5(
2212
- (item) => {
2213
- if (!item) return;
2214
- switch (item.type) {
2215
- case "dashboard":
2216
- open(`${WEB_APP_URL}/workspaces/${item.workspaceId}`).catch(() => {
2217
- });
2218
- break;
2219
- case "wake":
2220
- onWakeWorkspace(item.workspaceId);
2221
- break;
2222
- case "preview":
2223
- open(item.url).catch(() => {
2224
- });
2225
- break;
2226
- case "pr":
2227
- open(item.url).catch(() => {
2228
- });
2229
- break;
2230
- case "diff":
2231
- onViewDiff?.(item.diff, item.repoName);
2232
- break;
2233
- case "view-chat":
2234
- onSelectChatMode();
2235
- break;
2236
- case "view-diff":
2237
- onSelectDiffMode();
2238
- break;
2239
- case "createPr":
2240
- if (!isPlanMode) onCreatePr();
2241
- break;
2242
- }
2243
- },
2244
- [onWakeWorkspace, onCreatePr]
2245
- );
2246
- useKeyboard4((key) => {
2247
- if (!focused) return;
2248
- if (interactiveItems.length === 0) return;
2249
- if (key.name === "j" || key.name === "down") {
2250
- moveCursor(safeCursor + 1);
2251
- return;
2252
- }
2253
- if (key.name === "k" || key.name === "up") {
2254
- moveCursor(safeCursor - 1);
2255
- return;
2256
- }
2257
- if (key.name === "g" && !key.shift) {
2258
- moveCursor(0);
2259
- return;
2260
- }
2261
- if (key.name === "g" && key.shift) {
2262
- moveCursor(interactiveItems.length - 1);
2263
- return;
2264
- }
2265
- if (key.name === "enter" || key.name === "return") {
2266
- handleAction(interactiveItems[safeCursor]);
2267
- return;
2268
- }
2269
- if (key.name === "o") {
2270
- const dashboardItem2 = interactiveItems.find((item) => item.type === "dashboard");
2271
- if (dashboardItem2) handleAction(dashboardItem2);
2272
- return;
2273
- }
2274
- if (key.name === "w") {
2275
- const wakeItem2 = interactiveItems.find((item) => item.type === "wake");
2276
- if (wakeItem2) handleAction(wakeItem2);
2277
- return;
2278
- }
2279
- });
2280
- const isHighlighted = useCallback5(
2281
- (item) => {
2282
- if (!focused) return false;
2283
- const idx = interactiveItems.indexOf(item);
2284
- return idx === safeCursor;
2285
- },
2286
- [focused, interactiveItems, safeCursor]
2287
- );
2288
- const findItem = useCallback5(
2289
- (type, key) => {
2290
- return interactiveItems.find((item) => {
2291
- if (item.type !== type) return false;
2292
- if (type === "preview" && key) return item.port === Number(key);
2293
- if (type === "pr" && key) return item.repoName === key;
2294
- if (type === "diff" && key) return item.repoName === key;
2295
- return true;
2296
- });
2297
- },
2298
- [interactiveItems]
2299
- );
2300
- if (!workspaceName) {
2301
- return /* @__PURE__ */ jsx8(
2302
- "box",
2303
- {
2304
- width: 30,
2305
- border: true,
2306
- borderStyle: "rounded",
2307
- borderColor,
2308
- title: "Info",
2309
- titleAlignment: "center",
2310
- flexDirection: "column",
2311
- paddingX: 1,
2312
- backgroundColor: "#000000",
2313
- children: /* @__PURE__ */ jsx8("text", { fg: "#666666", children: "Select a workspace" })
2314
- }
2315
- );
2316
- }
2317
- if (loading || !status) {
2318
- return /* @__PURE__ */ jsx8(
2319
- "box",
2320
- {
2321
- width: 30,
2322
- border: true,
2323
- borderStyle: "rounded",
2324
- borderColor,
2325
- title: "Info",
2326
- titleAlignment: "center",
2327
- flexDirection: "column",
2328
- paddingX: 1,
2329
- backgroundColor: "#000000",
2330
- children: /* @__PURE__ */ jsx8("text", { fg: "#666666", children: "Loading..." })
2331
- }
2332
- );
2333
- }
2334
- const statusColor = status.status === "active" ? "#66bb6a" : status.status === "sleeping" ? "#ffaa00" : "#ff4444";
2335
- const rawEnv = status.environmentDetails;
2336
- const env = rawEnv && agentAvailability ? {
2337
- ...rawEnv,
2338
- claudeAuthMethod: agentAvailability.claude.available ? rawEnv.claudeAuthMethod : "none",
2339
- codexAuthMethod: agentAvailability.codex.available ? rawEnv.codexAuthMethod : "none"
2340
- } : rawEnv;
2341
- const dashboardItem = findItem("dashboard");
2342
- const wakeItem = findItem("wake");
2343
- const createPrItem = findItem("createPr");
2344
- return /* @__PURE__ */ jsx8(
2345
- "box",
2346
- {
2347
- width: 30,
2348
- border: true,
2349
- borderStyle: "rounded",
2350
- borderColor,
2351
- title: "Info",
2352
- titleAlignment: "center",
2353
- flexDirection: "column",
2354
- backgroundColor: "#000000",
2355
- children: /* @__PURE__ */ jsxs8("scrollbox", { ref: scrollboxRef, focused: false, flexGrow: 1, children: [
2356
- /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs8("box", { flexDirection: "column", children: [
2357
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: workspaceName }) }) }),
2358
- /* @__PURE__ */ jsxs8("text", { children: [
2359
- /* @__PURE__ */ jsxs8("span", { fg: statusColor, children: [
2360
- "\u25CF",
2361
- " ",
2362
- status.status
2363
- ] }),
2364
- env && /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
2365
- " ",
2366
- "\u2502",
2367
- " ",
2368
- env.engineVersion
2369
- ] })
2370
- ] })
2371
- ] }) }),
2372
- createPrItem && /* @__PURE__ */ jsx8(
2373
- InteractiveRow,
2374
- {
2375
- id: getItemId(createPrItem),
2376
- label: getItemLabel(createPrItem),
2377
- highlighted: isHighlighted(createPrItem),
2378
- disabled: isPlanMode,
2379
- onClick: () => handleAction(createPrItem)
2380
- }
2381
- ),
2382
- dashboardItem && /* @__PURE__ */ jsx8(
2383
- InteractiveRow,
2384
- {
2385
- id: getItemId(dashboardItem),
2386
- label: getItemLabel(dashboardItem),
2387
- highlighted: isHighlighted(dashboardItem),
2388
- onClick: () => handleAction(dashboardItem)
2389
- }
2390
- ),
2391
- wakeItem && /* @__PURE__ */ jsx8(
2392
- InteractiveRow,
2393
- {
2394
- id: getItemId(wakeItem),
2395
- label: getItemLabel(wakeItem),
2396
- highlighted: isHighlighted(wakeItem),
2397
- onClick: () => handleAction(wakeItem)
2398
- }
2399
- ),
2400
- (status.isClaudeProcessing || status.isCodexProcessing || status.isRelayProcessing) && /* @__PURE__ */ jsxs8("box", { backgroundColor: "#1a1500", paddingX: 1, marginX: 1, marginBottom: 1, children: [
2401
- status.isClaudeProcessing && /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#ffaa00", children: [
2402
- "\u25C6",
2403
- " Claude thinking..."
2404
- ] }) }),
2405
- status.isCodexProcessing && /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#ffaa00", children: [
2406
- "\u25C6",
2407
- " Codex thinking..."
2408
- ] }) }),
2409
- status.isRelayProcessing && /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#ffaa00", children: [
2410
- "\u25C6",
2411
- " Relay thinking..."
2412
- ] }) })
2413
- ] }),
2414
- env && /* @__PURE__ */ jsxs8(Section, { title: "Agents", children: [
2415
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2416
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "Claude" }) }),
2417
- /* @__PURE__ */ jsx8("text", { children: AUTH_METHOD_LABELS[env.claudeAuthMethod] ? /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.claudeAuthMethod] }) : /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" }) })
2418
- ] }),
2419
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2420
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "Codex" }) }),
2421
- /* @__PURE__ */ jsx8("text", { children: AUTH_METHOD_LABELS[env.codexAuthMethod] ? /* @__PURE__ */ jsx8("span", { fg: "#66bb6a", children: AUTH_METHOD_LABELS[env.codexAuthMethod] }) : /* @__PURE__ */ jsx8("span", { fg: "#ff4444", children: "\u2717" }) })
2422
- ] })
2423
- ] }),
2424
- /* @__PURE__ */ jsx8(Section, { title: "View", children: (() => {
2425
- const chatItem = findItem("view-chat");
2426
- const diffItem = findItem("view-diff");
2427
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
2428
- chatItem && /* @__PURE__ */ jsx8(
2429
- ViewModeRow,
2430
- {
2431
- id: getItemId(chatItem),
2432
- label: "1 Chat",
2433
- active: viewMode === "chat",
2434
- highlighted: isHighlighted(chatItem),
2435
- onClick: () => handleAction(chatItem)
2436
- }
2437
- ),
2438
- diffItem ? /* @__PURE__ */ jsx8(
2439
- ViewModeRow,
2440
- {
2441
- id: getItemId(diffItem),
2442
- label: "2 Diff",
2443
- active: viewMode === "diff",
2444
- highlighted: isHighlighted(diffItem),
2445
- onClick: () => handleAction(diffItem)
2446
- }
2447
- ) : /* @__PURE__ */ jsx8(ViewModeRow, { label: "2 Diff", active: false, highlighted: false, disabled: true, onClick: () => {
2448
- } }),
2449
- hasAnyDiff && diffItems.map((item, i) => {
2450
- const isActive = viewMode === "diff" && viewingDiffRepoName === item.repoName;
2451
- const cursorOn = isHighlighted(item);
2452
- const bg = cursorOn ? "#1a2a1a" : isActive ? "#0d2b0d" : "#111111";
2453
- const nameColor = isActive || cursorOn ? "#66bb6a" : "#cccccc";
2454
- return /* @__PURE__ */ jsxs8(
2455
- "box",
2456
- {
2457
- id: getItemId(item),
2458
- paddingX: 1,
2459
- backgroundColor: bg,
2460
- onMouseDown: () => handleAction(item),
2461
- flexDirection: "row",
2462
- justifyContent: "space-between",
2463
- children: [
2464
- /* @__PURE__ */ jsxs8("text", { children: [
2465
- /* @__PURE__ */ jsx8("span", { fg: isActive ? "#66bb6a" : "#555555", children: isActive ? " \u25B8 " : " \u2514 " }),
2466
- isActive ? /* @__PURE__ */ jsx8("span", { fg: nameColor, children: /* @__PURE__ */ jsx8("strong", { children: item.repoName }) }) : /* @__PURE__ */ jsx8("span", { fg: nameColor, children: item.repoName })
2467
- ] }),
2468
- /* @__PURE__ */ jsxs8("text", { children: [
2469
- item.added > 0 && /* @__PURE__ */ jsxs8("span", { fg: "#66bb6a", children: [
2470
- "+",
2471
- item.added
2472
- ] }),
2473
- item.added > 0 && item.removed > 0 && /* @__PURE__ */ jsx8("span", { fg: "#444444", children: " " }),
2474
- item.removed > 0 && /* @__PURE__ */ jsxs8("span", { fg: "#ff4444", children: [
2475
- "-",
2476
- item.removed
2477
- ] })
2478
- ] })
2479
- ]
2480
- },
2481
- `diff-${i}`
2482
- );
2483
- })
2484
- ] });
2485
- })() }),
2486
- env && /* @__PURE__ */ jsxs8(Section, { title: "Integrations", children: [
2487
- /* @__PURE__ */ jsx8(CardItem, { label: "GitHub", status: env.githubAccessConfigured }),
2488
- /* @__PURE__ */ jsx8(CardItem, { label: "Slack", status: env.slackAccessConfigured }),
2489
- /* @__PURE__ */ jsx8(CardItem, { label: "Linear", status: env.linearAccessConfigured })
2490
- ] }),
2491
- previews.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Previews", children: previews.map((preview, i) => {
2492
- const previewItem = findItem("preview", String(preview.port));
2493
- return /* @__PURE__ */ jsxs8(
2494
- "box",
2495
- {
2496
- id: previewItem ? getItemId(previewItem) : void 0,
2497
- flexDirection: "row",
2498
- justifyContent: "space-between",
2499
- paddingX: 1,
2500
- backgroundColor: previewItem && isHighlighted(previewItem) ? "#1a2a1a" : "#111111",
2501
- onMouseDown: () => previewItem && handleAction(previewItem),
2502
- children: [
2503
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsxs8("span", { fg: "#cccccc", children: [
2504
- ":",
2505
- preview.port
2506
- ] }) }),
2507
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: previewItem && isHighlighted(previewItem) ? "#66bb6a" : "#7dcfff", children: "\u2197" }) })
2508
- ]
2509
- },
2510
- i
2511
- );
2512
- }) }),
2513
- status.repoStatuses && status.repoStatuses.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Repositories", children: status.repoStatuses.map((repo, i) => {
2514
- const prItems = interactiveItems.filter(
2515
- (item) => item.type === "pr" && item.repoName === repo.name
2516
- );
2517
- return /* @__PURE__ */ jsxs8(React6.Fragment, { children: [
2518
- /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: repo.name }) }) }) }),
2519
- /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: /* @__PURE__ */ jsxs8("text", { children: [
2520
- /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
2521
- "\u2514",
2522
- " "
2523
- ] }),
2524
- /* @__PURE__ */ jsx8("span", { fg: repo.currentBranch !== repo.defaultBranch ? "#ffaa00" : "#66bb6a", children: repo.currentBranch })
2525
- ] }) }),
2526
- prItems.map((prItem) => /* @__PURE__ */ jsx8(
2527
- InteractiveRow,
2528
- {
2529
- id: getItemId(prItem),
2530
- label: ` ${getItemLabel(prItem)}`,
2531
- highlighted: isHighlighted(prItem),
2532
- onClick: () => handleAction(prItem)
2533
- },
2534
- getItemId(prItem)
2535
- ))
2536
- ] }, i);
2537
- }) }),
2538
- env && /* @__PURE__ */ jsxs8(Section, { title: "Hooks", children: [
2539
- /* @__PURE__ */ jsx8(CardItem, { label: "Global warm", status: env.globalWarmHookCompleted.status }),
2540
- env.repositories.map((repo, i) => /* @__PURE__ */ jsxs8(React6.Fragment, { children: [
2541
- /* @__PURE__ */ jsx8("box", { backgroundColor: "#111111", paddingX: 1, children: /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8("span", { fg: "#ffffff", children: /* @__PURE__ */ jsx8("strong", { children: repo.repositoryName }) }) }) }),
2542
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2543
- /* @__PURE__ */ jsxs8("text", { children: [
2544
- /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
2545
- "\u251C",
2546
- " "
2547
- ] }),
2548
- /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "warm" })
2549
- ] }),
2550
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status: repo.warmHookCompleted }) })
2551
- ] }),
2552
- /* @__PURE__ */ jsxs8("box", { flexDirection: "row", justifyContent: "space-between", paddingX: 1, backgroundColor: "#111111", children: [
2553
- /* @__PURE__ */ jsxs8("text", { children: [
2554
- /* @__PURE__ */ jsxs8("span", { fg: "#555555", children: [
2555
- "\u2514",
2556
- " "
2557
- ] }),
2558
- /* @__PURE__ */ jsx8("span", { fg: "#cccccc", children: "start" })
2559
- ] }),
2560
- /* @__PURE__ */ jsx8("text", { children: /* @__PURE__ */ jsx8(StatusDot, { status: repo.startHookCompleted }) })
2561
- ] })
2562
- ] }, i))
2563
- ] }),
2564
- env && env.skillsInstalled.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Skills", children: /* @__PURE__ */ jsx8(DetailList, { expected: [], actual: env.skillsInstalled }) }),
2565
- env && env.envVarsSet.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Env Vars", children: /* @__PURE__ */ jsx8(DetailList, { expected: [], actual: env.envVarsSet }) }),
2566
- env && env.filesUploaded.length > 0 && /* @__PURE__ */ jsx8(Section, { title: "Files", children: /* @__PURE__ */ jsx8(DetailList, { expected: [], actual: env.filesUploaded }) })
2567
- ] })
2568
- }
2569
- );
2570
- }
2571
-
2572
- // src/interactive/components/Toast.tsx
2573
- import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
2574
-
2575
- // src/interactive/toast-context.tsx
2576
- import { createContext as createContext2, useContext as useContext2, useState as useState7, useCallback as useCallback6, useRef as useRef4 } from "react";
2577
- import { jsx as jsx9 } from "@opentui/react/jsx-runtime";
2578
- var ToastContext = createContext2(null);
2579
- function useToast() {
2580
- const ctx = useContext2(ToastContext);
2581
- if (!ctx) throw new Error("useToast must be used within ToastProvider");
2582
- return ctx;
2583
- }
2584
- function ToastProvider({ children }) {
2585
- const [current, setCurrent] = useState7(null);
2586
- const timeoutRef = useRef4(null);
2587
- const show = useCallback6((options) => {
2588
- const { message, variant = "info", duration = 3e3 } = options;
2589
- setCurrent({ message, variant });
2590
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
2591
- timeoutRef.current = setTimeout(() => {
2592
- setCurrent(null);
2593
- }, duration);
2594
- }, []);
2595
- const error = useCallback6((err) => {
2596
- const message = err instanceof Error ? err.message : "An error occurred";
2597
- show({ message, variant: "error", duration: 5e3 });
2598
- }, [show]);
2599
- return /* @__PURE__ */ jsx9(ToastContext.Provider, { value: { current, show, error }, children });
2600
- }
2601
-
2602
- // src/interactive/components/Toast.tsx
2603
- import { jsx as jsx10 } from "@opentui/react/jsx-runtime";
2604
- var VARIANT_COLORS = {
2605
- info: { border: "#66bb6a", bg: "#0a1a0a" },
2606
- success: { border: "#66bb6a", bg: "#0a1a0a" },
2607
- warning: { border: "#ffaa00", bg: "#1a1a0a" },
2608
- error: { border: "#ff4444", bg: "#1a0a0a" }
2609
- };
2610
- function Toast() {
2611
- const { current } = useToast();
2612
- const { width } = useTerminalDimensions2();
2613
- if (!current) return null;
2614
- const colors = VARIANT_COLORS[current.variant] ?? VARIANT_COLORS.info;
2615
- const toastWidth = Math.min(60, width - 4);
2616
- return /* @__PURE__ */ jsx10(
2617
- "box",
2618
- {
2619
- position: "absolute",
2620
- bottom: 2,
2621
- right: 2,
2622
- width: toastWidth,
2623
- paddingX: 2,
2624
- paddingY: 1,
2625
- backgroundColor: colors.bg,
2626
- borderColor: colors.border,
2627
- border: true,
2628
- borderStyle: "rounded",
2629
- zIndex: 100,
2630
- children: /* @__PURE__ */ jsx10("text", { fg: "#ffffff", selectable: true, children: current.message })
2631
- }
2632
- );
2633
- }
2634
-
2635
- // src/interactive/App.tsx
2636
- import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
2637
- var FOCUS_ORDER = ["sidebar", "chat-tabs", "chat-history", "chat-input", "info"];
2638
- var MOCK_CHATS = [
2639
- { id: "mock-claude", provider: "claude", title: "Claude Code", createdAt: "", updatedAt: "", processing: false, parentChatId: null },
2640
- { id: "mock-codex", provider: "codex", title: "Codex", createdAt: "", updatedAt: "", processing: false, parentChatId: null }
2641
- ];
2642
- var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || "https://api.tryreplicas.com";
2643
- var workspacePollingStarted = false;
2644
- var queryClient = new QueryClient({
2645
- defaultOptions: {
2646
- queries: {
2647
- retry: 1,
2648
- refetchOnWindowFocus: false
2649
- }
2650
- }
2651
- });
2652
- var authValue = {
2653
- getAccessToken: () => getValidToken(),
2654
- getOrganizationId: () => getOrganizationId(),
2655
- monolithUrl: MONOLITH_URL,
2656
- queryClient
2657
- };
2658
- function App() {
2659
- return /* @__PURE__ */ jsx11(ReplicasAuthProvider, { value: authValue, children: /* @__PURE__ */ jsxs9(ToastProvider, { children: [
2660
- /* @__PURE__ */ jsx11(AppInner, {}),
2661
- /* @__PURE__ */ jsx11(Toast, {})
2662
- ] }) });
2663
- }
2664
- function AppInner() {
2665
- const renderer = useRenderer();
2666
- const { width } = useTerminalDimensions3();
2667
- const toast = useToast();
2668
- const rendererRef = useRef5(renderer);
2669
- rendererRef.current = renderer;
2670
- if (!workspacePollingStarted) {
2671
- workspacePollingStarted = true;
2672
- setInterval(() => {
2673
- queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2674
- rendererRef.current.requestRender();
2675
- }, 3e4);
2676
- }
2677
- useEffect3(() => {
2678
- const handler = (selection) => {
2679
- const text = selection.getSelectedText();
2680
- if (text) {
2681
- renderer.copyToClipboardOSC52(text);
2682
- toast.show({ message: "Copied to clipboard", variant: "info", duration: 1500 });
2683
- }
2684
- };
2685
- renderer.on("selection", handler);
2686
- return () => {
2687
- renderer.off("selection", handler);
2688
- };
2689
- }, [renderer, toast]);
2690
- const [selectedWorkspaceId, setSelectedWorkspaceId] = useState8(null);
2691
- const [selectedChatId, setSelectedChatId] = useState8(null);
2692
- const [focusPanel, setFocusPanel] = useState8("sidebar");
2693
- const [taskMode, setTaskMode] = useState8("build");
2694
- const [mockWorkspaces, setMockWorkspaces] = useState8([]);
2695
- const mockToRealRef = useRef5(/* @__PURE__ */ new Map());
2696
- const mockGroupRef = useRef5(/* @__PURE__ */ new Map());
2697
- const mockPollingRef = useRef5(null);
2698
- if (mockWorkspaces.length > 0 && !mockPollingRef.current) {
2699
- mockPollingRef.current = setInterval(() => {
2700
- queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2701
- rendererRef.current.requestRender();
2702
- }, 5e3);
2703
- } else if (mockWorkspaces.length === 0 && mockPollingRef.current) {
2704
- clearInterval(mockPollingRef.current);
2705
- mockPollingRef.current = null;
2706
- }
2707
- const [mockMessages, setMockMessages] = useState8([]);
2708
- const [mockThinking, setMockThinking] = useState8(false);
2709
- const pendingMessageRef = useRef5(/* @__PURE__ */ new Map());
2710
- const [viewingDiff, setViewingDiff] = useState8(null);
2711
- const [lastViewedDiff, setLastViewedDiff] = useState8(null);
2712
- const mockIds = useMemo6(() => new Set(mockWorkspaces.map((m) => m.id)), [mockWorkspaces]);
2713
- const isMockSelected = selectedWorkspaceId ? mockIds.has(selectedWorkspaceId) : false;
2714
- const { data: workspacesData, isLoading: loadingWorkspaces } = useWorkspaces(1, 100, "organization");
2715
- const { data: setsData } = useRepositorySets();
2716
- const { data: envsData } = useEnvironments();
2717
- const { data: statusData, isLoading: loadingStatus } = useWorkspaceStatus(
2718
- isMockSelected ? null : selectedWorkspaceId,
2719
- { includeDiffs: true }
2720
- );
2721
- const { data: chatsData } = useWorkspaceChats(isMockSelected ? null : selectedWorkspaceId);
2722
- const chats = isMockSelected ? MOCK_CHATS : chatsData?.chats ?? [];
2723
- const resolvedChatId = useMemo6(() => {
2724
- if (selectedChatId && chats.find((c) => c.id === selectedChatId)) {
2725
- return selectedChatId;
2726
- }
2727
- return getInitialChatId(chats);
2728
- }, [chats, selectedChatId]);
2729
- const { data: historyData, isLoading: loadingMessages } = useChatHistory(
2730
- isMockSelected ? null : selectedWorkspaceId,
2731
- resolvedChatId?.startsWith("mock-") ? null : resolvedChatId
2732
- );
2733
- const { data: previewsData } = useWorkspacePreviews(isMockSelected ? null : selectedWorkspaceId);
2734
- const { connected: sseConnected } = useWorkspaceEvents(
2735
- isMockSelected ? null : selectedWorkspaceId
2736
- );
2737
- const { data: agentAvailability } = useAgentAvailability(isMockSelected ? null : selectedWorkspaceId);
2738
- const createWorkspaceMutation = useCreateWorkspace();
2739
- const deleteWorkspaceMutation = useDeleteWorkspace();
2740
- const wakeWorkspaceMutation = useWakeWorkspace();
2741
- const generateNameMutation = useGenerateWorkspaceName();
2742
- const [wakingWorkspaceId, setWakingWorkspaceId] = useState8(null);
2743
- const sendMessageMutation = useSendChatMessage(selectedWorkspaceId, resolvedChatId);
2744
- const interruptMutation = useInterruptChat(selectedWorkspaceId, resolvedChatId);
2745
- const workspaces = workspacesData?.workspaces ?? [];
2746
- const repositorySets = setsData?.repository_sets ?? [];
2747
- const environments = envsData?.environments ?? [];
2748
- const previews = previewsData?.previews ?? [];
2749
- const allWorkspaces = useMemo6(() => [...mockWorkspaces, ...workspaces], [mockWorkspaces, workspaces]);
2750
- const groups = useMemo6(
2751
- () => buildGroups(allWorkspaces, workspacesData, environments, repositorySets, mockGroupRef.current),
2752
- [allWorkspaces, workspacesData, environments, repositorySets]
2753
- );
2754
- const selectedWorkspace = allWorkspaces.find((ws) => ws.id === selectedWorkspaceId) ?? null;
2755
- const selectedChat = chats.find((c) => c.id === resolvedChatId) ?? null;
2756
- const isProcessing = mockThinking || (selectedChat?.processing ?? false);
2757
- const selectedAgent = selectedChat?.provider ?? "claude";
2758
- const agentAvailable = agentAvailability ? selectedAgent === "codex" ? agentAvailability.codex.available : agentAvailability.claude.available : true;
2759
- const agentLabel = selectedAgent === "codex" ? "Codex" : "Claude Code";
2760
- const displayMessages = useMemo6(() => {
2761
- if (isMockSelected) return mockMessages;
2762
- const rawEvents = historyData?.events ?? [];
2763
- const events = rawEvents.filter(isAgentBackendEvent);
2764
- if (events.length === 0) return [];
2765
- const provider = selectedChat?.provider ?? "claude";
2766
- const parsed = parseAgentEvents(events, provider);
2767
- return filterDisplayMessages(parsed, provider);
2768
- }, [isMockSelected, mockMessages, historyData, selectedChat?.provider]);
2769
- const showInfoPanel = width >= 100;
2770
- const showWorkspacePanel = width >= 60;
2771
- useEffect3(() => {
2772
- if (!selectedWorkspaceId && workspaces.length > 0) {
2773
- setSelectedWorkspaceId(workspaces[0].id);
2774
- }
2775
- }, [workspaces, selectedWorkspaceId]);
2776
- useEffect3(() => {
2777
- if (mockWorkspaces.length === 0) return;
2778
- const realIds = new Set(workspaces.map((w) => w.id));
2779
- let changed = false;
2780
- const remaining = mockWorkspaces.filter((mock) => {
2781
- const realId = mockToRealRef.current.get(mock.id);
2782
- if (realId && realIds.has(realId)) {
2783
- if (selectedWorkspaceId === mock.id) {
2784
- setSelectedWorkspaceId(realId);
2785
- }
2786
- const pending = pendingMessageRef.current.get(mock.id);
2787
- if (pending) {
2788
- pendingMessageRef.current.delete(mock.id);
2789
- pendingMessageRef.current.set(realId, pending);
2790
- }
2791
- mockToRealRef.current.delete(mock.id);
2792
- mockGroupRef.current.delete(mock.id);
2793
- setSelectedChatId(null);
2794
- setMockMessages([]);
2795
- setMockThinking(false);
2796
- changed = true;
2797
- return false;
2798
- }
2799
- return true;
2800
- });
2801
- if (changed) setMockWorkspaces(remaining);
2802
- }, [workspaces, mockWorkspaces, selectedWorkspaceId]);
2803
- useEffect3(() => {
2804
- if (!selectedWorkspaceId || isMockSelected) return;
2805
- if (!resolvedChatId || resolvedChatId.startsWith("mock-")) return;
2806
- if (statusData?.status !== "active") return;
2807
- const pending = pendingMessageRef.current.get(selectedWorkspaceId);
2808
- if (!pending) return;
2809
- pendingMessageRef.current.delete(selectedWorkspaceId);
2810
- const noBranches = statusData?.repoStatuses && statusData.repoStatuses.length > 0 && statusData.repoStatuses.every((r) => r.currentBranch === r.defaultBranch);
2811
- if (noBranches) {
2812
- generateNameMutation.mutateAsync({ workspaceId: selectedWorkspaceId, prompt: pending }).catch(() => {
2813
- });
2814
- }
2815
- sendMessageMutation.mutate({ message: pending });
2816
- }, [selectedWorkspaceId, resolvedChatId, isMockSelected, statusData?.status]);
2817
- const handleSelectWorkspace = useCallback7((workspaceId) => {
2818
- setSelectedWorkspaceId(workspaceId);
2819
- setSelectedChatId(null);
2820
- setMockMessages([]);
2821
- setMockThinking(false);
2822
- setViewingDiff(null);
2823
- setLastViewedDiff(null);
2824
- setFocusPanel("chat-input");
2825
- }, []);
2826
- const handleFocus = useCallback7((panel) => {
2827
- if (panel === "sidebar") {
2828
- queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2829
- }
2830
- setFocusPanel(panel);
2831
- }, []);
2832
- const handleCreateWorkspace = useCallback7(
2833
- async (group) => {
2834
- if (group.id === "__ungrouped__") {
2835
- toast.error("Cannot create a workspace in the Other / Ungrouped folder");
2836
- return;
2837
- }
2838
- const orgId = getOrganizationId() ?? "";
2839
- const mock = createMockWorkspaceRecord(orgId);
2840
- mockGroupRef.current.set(mock.id, group.id);
2841
- setMockWorkspaces((prev) => [mock, ...prev]);
2842
- setSelectedWorkspaceId(mock.id);
2843
- setViewingDiff(null);
2844
- setLastViewedDiff(null);
2845
- setFocusPanel("chat-input");
2846
- try {
2847
- const request = {
2848
- environment_id: group.environmentId,
2849
- name: mock.name,
2850
- placeholder: true
2851
- };
2852
- const result = await createWorkspaceMutation.mutateAsync(request);
2853
- mockToRealRef.current.set(mock.id, result.workspace.id);
2854
- } catch (err) {
2855
- mockGroupRef.current.delete(mock.id);
2856
- setMockWorkspaces((prev) => prev.filter((m) => m.id !== mock.id));
2857
- toast.error(err);
2858
- }
2859
- },
2860
- [createWorkspaceMutation, toast]
2861
- );
2862
- const handleDeleteWorkspace = useCallback7(
2863
- async (workspaceId) => {
2864
- if (mockIds.has(workspaceId)) {
2865
- mockGroupRef.current.delete(workspaceId);
2866
- mockToRealRef.current.delete(workspaceId);
2867
- setMockWorkspaces((prev) => prev.filter((m) => m.id !== workspaceId));
2868
- if (selectedWorkspaceId === workspaceId) {
2869
- setSelectedWorkspaceId(null);
2870
- setViewingDiff(null);
2871
- setLastViewedDiff(null);
2872
- }
2873
- return;
2874
- }
2875
- try {
2876
- await deleteWorkspaceMutation.mutateAsync(workspaceId);
2877
- if (selectedWorkspaceId === workspaceId) {
2878
- setSelectedWorkspaceId(null);
2879
- setViewingDiff(null);
2880
- setLastViewedDiff(null);
2881
- }
2882
- toast.show({ message: "Workspace deleted", variant: "info" });
2883
- } catch (err) {
2884
- toast.error(err);
2885
- }
2886
- },
2887
- [mockIds, selectedWorkspaceId, deleteWorkspaceMutation]
2888
- );
2889
- const handleWakeWorkspace = useCallback7(
2890
- async (workspaceId) => {
2891
- setWakingWorkspaceId(workspaceId);
2892
- try {
2893
- await wakeWorkspaceMutation.mutateAsync(workspaceId);
2894
- toast.show({ message: "Workspace waking up...", variant: "info" });
2895
- } catch (err) {
2896
- toast.error(err);
2897
- } finally {
2898
- setWakingWorkspaceId(null);
2899
- }
2900
- },
2901
- [wakeWorkspaceMutation, toast]
2902
- );
2903
- const handleSelectChat = useCallback7((chatId) => {
2904
- setSelectedChatId(chatId);
2905
- }, []);
2906
- useKeyboard5((key) => {
2907
- if (key.name === "f12") {
2908
- renderer.console.toggle();
2909
- return;
2910
- }
2911
- if (key.ctrl && key.name === "c") {
2912
- renderer.destroy();
2913
- return;
2914
- }
2915
- if (key.name === "escape") {
2916
- if (isProcessing && selectedWorkspaceId && resolvedChatId) {
2917
- interruptMutation.mutate();
2918
- }
2919
- return;
2920
- }
2921
- if (key.shift && key.name === "tab") {
2922
- setFocusPanel((current) => {
2923
- const availablePanels = FOCUS_ORDER.filter((p) => {
2924
- if (p === "sidebar" && !showWorkspacePanel) return false;
2925
- if ((p === "chat-tabs" || p === "chat-history" || p === "chat-input") && (!selectedWorkspaceId || statusData?.status === "sleeping")) return false;
2926
- if (viewingDiff && (p === "chat-tabs" || p === "chat-input")) return false;
2927
- if (p === "chat-tabs" && chats.length <= 1) return false;
2928
- if (p === "info" && !showInfoPanel) return false;
2929
- return true;
2930
- });
2931
- const idx = availablePanels.indexOf(current);
2932
- const next = availablePanels[(idx + 1) % availablePanels.length];
2933
- if (next === "sidebar") {
2934
- queryClient.invalidateQueries({ queryKey: ["workspaces"] });
2935
- }
2936
- return next;
2937
- });
2938
- return;
2939
- }
2940
- if (key.name === "tab" && focusPanel === "chat-input") {
2941
- setTaskMode((m) => m === "build" ? "plan" : "build");
2942
- return;
2943
- }
2944
- if ((key.name === "1" || key.name === "2") && focusPanel !== "chat-input") {
2945
- if (key.name === "1") {
2946
- setViewingDiff(null);
2947
- } else if (firstAvailableDiff) {
2948
- setViewingDiff(firstAvailableDiff);
2949
- setLastViewedDiff(firstAvailableDiff);
2950
- }
2951
- return;
2952
- }
2953
- if (key.name === "w" && focusPanel !== "sidebar" && focusPanel !== "info" && selectedWorkspaceId) {
2954
- if (statusData?.status === "sleeping" && !wakingWorkspaceId) {
2955
- handleWakeWorkspace(selectedWorkspaceId);
2956
- }
2957
- return;
2958
- }
2959
- if (focusPanel === "chat-input") return;
2960
- });
2961
- const handleViewDiff = useCallback7((diff, repoName) => {
2962
- setViewingDiff({ diff, repoName });
2963
- setLastViewedDiff({ diff, repoName });
2964
- setFocusPanel("chat-history");
2965
- }, []);
2966
- const handleSelectChatMode = useCallback7(() => {
2967
- setViewingDiff(null);
2968
- setFocusPanel("chat-history");
2969
- }, []);
2970
- const firstAvailableDiff = useMemo6(() => {
2971
- if (lastViewedDiff) return lastViewedDiff;
2972
- const repos = statusData?.repoStatuses ?? [];
2973
- for (const repo of repos) {
2974
- if (repo.gitDiff?.fullDiff && (repo.gitDiff.added > 0 || repo.gitDiff.removed > 0)) {
2975
- return { diff: repo.gitDiff.fullDiff, repoName: repo.name };
2976
- }
2977
- }
2978
- return null;
2979
- }, [lastViewedDiff, statusData?.repoStatuses]);
2980
- const handleSelectDiffMode = useCallback7(() => {
2981
- if (firstAvailableDiff) {
2982
- setViewingDiff(firstAvailableDiff);
2983
- setLastViewedDiff(firstAvailableDiff);
2984
- setFocusPanel("chat-history");
2985
- }
2986
- }, [firstAvailableDiff]);
2987
- const handleSendMessage = useCallback7(
2988
- async (message) => {
2989
- if (!selectedWorkspaceId || !resolvedChatId) return;
2990
- if (isMockSelected) {
2991
- const { message: userMsg } = createUserMessage(message);
2992
- setMockMessages((prev) => [...prev, userMsg]);
2993
- pendingMessageRef.current.set(selectedWorkspaceId, message);
2994
- setMockThinking(true);
2995
- return;
2996
- }
2997
- if (!agentAvailable) {
2998
- toast.error(new Error(`${agentLabel} is not connected. Run \`replicas ${selectedAgent === "codex" ? "codex-auth" : "claude-auth"}\` to connect.`));
2999
- return;
3000
- }
3001
- sendMessageMutation.mutate({
3002
- message,
3003
- permissionMode: taskMode === "plan" ? "read" : void 0
3004
- });
3005
- },
3006
- [selectedWorkspaceId, resolvedChatId, isMockSelected, sendMessageMutation, taskMode, agentAvailable, agentLabel, selectedAgent, toast.error]
3007
- );
3008
- return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#000000", children: [
3009
- /* @__PURE__ */ jsxs9("box", { flexDirection: "row", flexGrow: 1, backgroundColor: "#000000", children: [
3010
- showWorkspacePanel && /* @__PURE__ */ jsx11(
3011
- WorkspaceSidebar,
3012
- {
3013
- groups,
3014
- selectedWorkspaceId,
3015
- mockIds,
3016
- onSelectWorkspace: handleSelectWorkspace,
3017
- onCreateWorkspace: handleCreateWorkspace,
3018
- onDeleteWorkspace: handleDeleteWorkspace,
3019
- onWakeWorkspace: handleWakeWorkspace,
3020
- wakingWorkspaceId,
3021
- focused: focusPanel === "sidebar",
3022
- loading: loadingWorkspaces
3023
- }
3024
- ),
3025
- /* @__PURE__ */ jsx11("box", { flexDirection: "column", flexGrow: 1, children: viewingDiff ? /* @__PURE__ */ jsx11(
3026
- "box",
3027
- {
3028
- flexGrow: 1,
3029
- border: true,
3030
- borderStyle: "rounded",
3031
- borderColor: focusPanel !== "sidebar" && focusPanel !== "info" ? "#66bb6a" : "#333333",
3032
- backgroundColor: "#000000",
3033
- flexDirection: "column",
3034
- title: "Diff",
3035
- titleAlignment: "center",
3036
- children: /* @__PURE__ */ jsx11(DiffViewer, { diff: viewingDiff.diff, repoName: viewingDiff.repoName, focused: focusPanel !== "sidebar" && focusPanel !== "info" })
3037
- }
3038
- ) : selectedWorkspaceId && statusData?.status !== "sleeping" ? /* @__PURE__ */ jsx11(
3039
- ChatArea,
3040
- {
3041
- chats,
3042
- selectedChatId: resolvedChatId,
3043
- displayMessages,
3044
- onSelectChat: handleSelectChat,
3045
- onSendMessage: handleSendMessage,
3046
- onFocus: handleFocus,
3047
- focusPanel,
3048
- taskMode,
3049
- isProcessing,
3050
- loading: loadingMessages,
3051
- agentAvailable,
3052
- agentLabel
3053
- }
3054
- ) : selectedWorkspaceId && statusData?.status === "sleeping" ? /* @__PURE__ */ jsxs9(
3055
- "box",
3056
- {
3057
- flexGrow: 1,
3058
- border: true,
3059
- borderStyle: "rounded",
3060
- borderColor: "#333333",
3061
- backgroundColor: "#000000",
3062
- justifyContent: "center",
3063
- alignItems: "center",
3064
- flexDirection: "column",
3065
- gap: 1,
3066
- children: [
3067
- /* @__PURE__ */ jsxs9("text", { fg: "#888888", children: [
3068
- "\u263E",
3069
- " This workspace is sleeping"
3070
- ] }),
3071
- /* @__PURE__ */ jsxs9("text", { fg: "#555555", children: [
3072
- "Press ",
3073
- /* @__PURE__ */ jsx11("span", { fg: "#66bb6a", children: /* @__PURE__ */ jsx11("strong", { children: "w" }) }),
3074
- " to wake it up"
3075
- ] })
3076
- ]
3077
- }
3078
- ) : /* @__PURE__ */ jsx11(
3079
- "box",
3080
- {
3081
- flexGrow: 1,
3082
- border: true,
3083
- borderStyle: "rounded",
3084
- borderColor: "#333333",
3085
- backgroundColor: "#000000",
3086
- justifyContent: "center",
3087
- alignItems: "center",
3088
- children: /* @__PURE__ */ jsx11("text", { fg: "#555555", children: "Create a workspace to begin building!" })
3089
- }
3090
- ) }),
3091
- showInfoPanel && /* @__PURE__ */ jsx11(
3092
- WorkspaceInfo,
3093
- {
3094
- status: statusData ?? null,
3095
- workspaceName: selectedWorkspace?.name ?? null,
3096
- workspaceId: selectedWorkspaceId,
3097
- focused: focusPanel === "info",
3098
- loading: loadingStatus,
3099
- previews,
3100
- agentAvailability: agentAvailability ?? null,
3101
- onWakeWorkspace: handleWakeWorkspace,
3102
- onViewDiff: handleViewDiff,
3103
- onCreatePr: () => {
3104
- if (!resolvedChatId) return;
3105
- sendMessageMutation.mutate({ message: "Push changes and create a GitHub PR" });
3106
- },
3107
- isPlanMode: taskMode === "plan",
3108
- wakingWorkspaceId,
3109
- viewMode: viewingDiff ? "diff" : "chat",
3110
- viewingDiffRepoName: viewingDiff?.repoName ?? null,
3111
- onSelectChatMode: handleSelectChatMode,
3112
- onSelectDiffMode: handleSelectDiffMode
3113
- }
3114
- )
3115
- ] }),
3116
- /* @__PURE__ */ jsx11(
3117
- StatusBar,
3118
- {
3119
- focusPanel,
3120
- viewingDiff: !!viewingDiff,
3121
- hasDiffAvailable: !!firstAvailableDiff
3122
- }
3123
- )
3124
- ] });
3125
- }
3126
-
3127
- // src/interactive/index.tsx
3128
- import { jsx as jsx12 } from "@opentui/react/jsx-runtime";
3129
- async function launchInteractive() {
3130
- const renderer = await createCliRenderer({
3131
- exitOnCtrlC: false
3132
- });
3133
- createRoot(renderer).render(/* @__PURE__ */ jsx12(App, {}));
3134
- }
3135
- export {
3136
- launchInteractive
3137
- };