ompchamber 0.8.0 → 1.0.0
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.
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/{index.5c0447cefe.css → index.e880517fcd.css} +1 -1
- package/dist/client/static/js/async/g.a06ca3b3e9.js +2 -0
- package/dist/client/static/js/async/{m.0896dab823.js → m.c038c71b9f.js} +2 -2
- package/dist/client/static/js/async/ms.98960af4eb.js +67 -0
- package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +12 -0
- package/dist/client/static/js/async/ot.84c71b308f.js +2 -0
- package/dist/client/static/js/async/{x4.69ec69c61b.js.LICENSE.txt → ot.84c71b308f.js.LICENSE.txt} +5 -0
- package/dist/client/static/js/{index.dc5f5fabe8.js → index.0de15c5e17.js} +10 -10
- package/package.json +2 -1
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +1 -1
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +1 -1
- package/src/client/components/layout/session-sidebar/SessionList.tsx +1 -1
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +1 -1
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +1 -1
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +2 -2
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +11 -4
- package/src/client/components/workspace/chat-timeline/QueueList.tsx +18 -14
- package/src/client/components/workspace/chat-timeline/index.tsx +4 -2
- package/src/client/components/workspace/git-panel/BranchToolbar.tsx +2 -2
- package/src/client/components/workspace/git-panel/index.tsx +5 -1
- package/src/client/components/workspace/terminal-panel/Header.tsx +51 -19
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +86 -282
- package/src/client/components/workspace/terminal-panel/index.tsx +76 -50
- package/src/client/components/workspace/terminal-panel/mount.ts +275 -0
- package/src/client/data/theme/terminal.ts +27 -29
- package/src/client/hooks/chat/omp/session-list.tsx +19 -2
- package/src/client/hooks/chat/omp/session-sidebar-controller.ts +1 -1
- package/src/client/hooks/chat/timeline/actions.ts +23 -25
- package/src/client/hooks/chat/timeline/index.ts +12 -20
- package/src/client/hooks/chat/timeline/queue.ts +110 -46
- package/src/client/hooks/workspace/terminal/index.ts +217 -0
- package/src/client/hooks/workspace/terminal/socket.ts +227 -0
- package/src/server/db.server.ts +19 -0
- package/src/server/lib/fs/git-sync.ts +191 -0
- package/src/server/lib/omp/rpc/manager.ts +58 -3
- package/src/server/lib/omp/rpc/session-commands.ts +6 -0
- package/src/server/lib/omp/session/sidebar-data.server.ts +3 -3
- package/src/server/lib/queue/delivery.server.ts +114 -0
- package/src/server/lib/queue/schema-migration.server.ts +50 -0
- package/src/server/lib/queue/store.server.ts +185 -0
- package/src/server/lib/terminal/foreground.ts +73 -0
- package/src/server/lib/terminal/history.test.ts +118 -0
- package/src/server/lib/terminal/history.ts +205 -0
- package/src/server/lib/terminal/runtime.server.ts +338 -0
- package/src/server/lib/terminal/session.server.ts +215 -0
- package/src/server/lib/terminal/shell.test.ts +97 -0
- package/src/server/lib/terminal/shell.ts +103 -0
- package/src/server/routes/fs/git.ts +22 -12
- package/src/server/routes/index.ts +2 -1
- package/src/server/routes/sessions/index.ts +11 -3
- package/src/server/routes/sessions/queue.ts +121 -0
- package/src/server/routes/sessions/session.ts +1 -132
- package/src/server/routes/settings/commands.ts +1 -1
- package/src/server/routes/terminal/index.ts +2 -2
- package/src/server/routes/terminal/run.ts +8 -100
- package/src/server/routes/terminal/sessions.ts +24 -0
- package/src/server/routes/terminal/ws.ts +149 -0
- package/src/shared/lib/chat/omp/agent-events.test.ts +37 -1
- package/src/shared/lib/chat/omp/agent-events.ts +30 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +9 -1
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +45 -8
- package/src/shared/lib/omp/session/stream-state.server.ts +2 -3
- package/src/shared/lib/workspace/session-state/store.ts +1 -4
- package/src/shared/lib/workspace/terminal/protocol.ts +222 -0
- package/src/shared/types/omp/agent.ts +12 -0
- package/src/shared/types/terminal.ts +17 -15
- package/src/shared/types/workspace.ts +2 -2
- package/dist/client/static/js/async/g.d1c2fe09aa.js +0 -2
- package/dist/client/static/js/async/x4.69ec69c61b.js +0 -6
- package/src/client/components/workspace/terminal-panel/InputBar.tsx +0 -82
- package/src/client/components/workspace/terminal-panel/LogEntry.tsx +0 -70
- package/src/client/hooks/workspace/terminal-output.ts +0 -66
- package/src/client/hooks/workspace/terminal.ts +0 -283
- package/src/server/lib/fs/terminal-cwd.ts +0 -49
- package/src/server/routes/terminal/stream.ts +0 -100
- /package/dist/client/static/js/async/{g.d1c2fe09aa.js.LICENSE.txt → g.a06ca3b3e9.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.0896dab823.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.dc5f5fabe8.js.LICENSE.txt → index.0de15c5e17.js.LICENSE.txt} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ompchamber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Developer web console and diagnostic chamber for AI Oh-My-Pi — Elysia + Preact on Bun.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Wijanarko Putra Rajeb <wijanarko.rajeb@gmail.com>",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@shikijs/themes": "^4.4.3",
|
|
52
52
|
"@tailwindcss/typography": "^0.5.20",
|
|
53
53
|
"@xterm/addon-fit": "^0.11.0",
|
|
54
|
+
"@xterm/addon-webgl": "^0.19.0",
|
|
54
55
|
"@xterm/xterm": "^6.0.0",
|
|
55
56
|
"dompurify": "^3.4.15",
|
|
56
57
|
"elysia": "^1.4.30",
|
|
@@ -29,7 +29,7 @@ export function Category({
|
|
|
29
29
|
onNewSessionForFolder: (id: number) => void;
|
|
30
30
|
forceExpanded?: boolean;
|
|
31
31
|
showArchived?: boolean;
|
|
32
|
-
sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'
|
|
32
|
+
sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'>;
|
|
33
33
|
}) {
|
|
34
34
|
const [isOpen, setIsOpen] = useState(folder.isExpanded || false);
|
|
35
35
|
const [showMenu, setShowMenu] = useState(false);
|
|
@@ -6,7 +6,7 @@ export interface SessionItemProps {
|
|
|
6
6
|
title: string;
|
|
7
7
|
isActive?: boolean;
|
|
8
8
|
isArchived?: boolean;
|
|
9
|
-
status?: 'stream' | 'finish' | 'abort'
|
|
9
|
+
status?: 'stream' | 'finish' | 'abort';
|
|
10
10
|
/** The session's agent is blocked on a question until the user answers it. */
|
|
11
11
|
awaitingInput?: boolean;
|
|
12
12
|
onClick?: () => void;
|
|
@@ -6,7 +6,7 @@ interface SessionSidebarSessionListProps {
|
|
|
6
6
|
activeSessionId: number | string | null;
|
|
7
7
|
searchQuery: string;
|
|
8
8
|
showArchived: boolean;
|
|
9
|
-
sessionStatus: Record<string, 'stream' | 'finish' | 'abort'
|
|
9
|
+
sessionStatus: Record<string, 'stream' | 'finish' | 'abort'>;
|
|
10
10
|
isScrolling: boolean;
|
|
11
11
|
onScroll: () => void;
|
|
12
12
|
onSelectSession: (id: number | string) => void;
|
|
@@ -17,7 +17,7 @@ interface MobileSessionCategoryProps {
|
|
|
17
17
|
isExpanded: boolean;
|
|
18
18
|
onToggleExpand: () => void;
|
|
19
19
|
showArchived?: boolean;
|
|
20
|
-
sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'
|
|
20
|
+
sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function MobileSessionCategory({
|
|
@@ -7,7 +7,7 @@ interface MobileSessionListProps {
|
|
|
7
7
|
activeSessionId: number | string | null;
|
|
8
8
|
expandedFolders: Record<number, boolean>;
|
|
9
9
|
showArchived: boolean;
|
|
10
|
-
sessionStatus: Record<string, 'stream' | 'finish' | 'abort'
|
|
10
|
+
sessionStatus: Record<string, 'stream' | 'finish' | 'abort'>;
|
|
11
11
|
isScrolling: boolean;
|
|
12
12
|
onScroll: () => void;
|
|
13
13
|
onSelectSession: (id: number | string) => void;
|
|
@@ -5,7 +5,7 @@ import { useInlineRename } from '@/client/hooks/ui/inline-rename';
|
|
|
5
5
|
export interface MobileSessionRowProps {
|
|
6
6
|
session: SessionItemData;
|
|
7
7
|
isActive: boolean;
|
|
8
|
-
status?: 'stream' | 'finish' | 'abort'
|
|
8
|
+
status?: 'stream' | 'finish' | 'abort';
|
|
9
9
|
/** Relative age of the session, or null when no usable timestamp exists. */
|
|
10
10
|
timeAgo: string | null;
|
|
11
11
|
showTreeGlyph?: boolean;
|
|
@@ -64,7 +64,7 @@ export function MobileSessionRow({
|
|
|
64
64
|
<div className="flex items-center space-x-1.5 min-w-0 pr-2">
|
|
65
65
|
<span className="w-4 flex-shrink-0 flex items-center justify-center">
|
|
66
66
|
{status === 'stream' && <Loader2 size={13} className="text-ink/50 animate-spin" />}
|
|
67
|
-
{(status === 'finish' || status === 'abort'
|
|
67
|
+
{(status === 'finish' || status === 'abort') && <Check size={13} className="text-ink/50" />}
|
|
68
68
|
</span>
|
|
69
69
|
{showTreeGlyph && <span className="text-ink/40 text-xs flex-shrink-0 font-mono">></span>}
|
|
70
70
|
<span className="text-xs truncate leading-snug">
|
|
@@ -13,7 +13,8 @@ interface ComposerDockProps {
|
|
|
13
13
|
provider?: string;
|
|
14
14
|
providerNames?: Record<string, string>;
|
|
15
15
|
messageQueue: QueuedMessage[];
|
|
16
|
-
|
|
16
|
+
onRemoveQueueItem: (id: string) => void;
|
|
17
|
+
onReorderQueue: (orderedIds: string[]) => void;
|
|
17
18
|
onEditQueueItem: (item: QueuedMessage) => void;
|
|
18
19
|
onSendNowQueueItem: (item: QueuedMessage) => void;
|
|
19
20
|
steeringQueue: QueuedMessage[];
|
|
@@ -49,7 +50,8 @@ export function ComposerDock({
|
|
|
49
50
|
provider,
|
|
50
51
|
providerNames,
|
|
51
52
|
messageQueue,
|
|
52
|
-
|
|
53
|
+
onRemoveQueueItem,
|
|
54
|
+
onReorderQueue,
|
|
53
55
|
onEditQueueItem,
|
|
54
56
|
onSendNowQueueItem,
|
|
55
57
|
steeringQueue,
|
|
@@ -87,13 +89,18 @@ export function ComposerDock({
|
|
|
87
89
|
)}
|
|
88
90
|
<QueueList
|
|
89
91
|
queue={messageQueue}
|
|
90
|
-
|
|
92
|
+
onRemove={onRemoveQueueItem}
|
|
93
|
+
onReorder={onReorderQueue}
|
|
91
94
|
onEdit={onEditQueueItem}
|
|
92
95
|
onSendNow={onSendNowQueueItem}
|
|
93
96
|
/>
|
|
94
97
|
<QueueList
|
|
95
98
|
queue={steeringQueue}
|
|
96
|
-
|
|
99
|
+
onRemove={(id) => setSteeringQueue((q) => q.filter((i) => i.id !== id))}
|
|
100
|
+
onReorder={(orderedIds) => setSteeringQueue((prev) => {
|
|
101
|
+
const byId = new Map(prev.map((i) => [i.id, i]));
|
|
102
|
+
return orderedIds.map((id) => byId.get(id)).filter((i): i is QueuedMessage => Boolean(i));
|
|
103
|
+
})}
|
|
97
104
|
isSteering
|
|
98
105
|
/>
|
|
99
106
|
<ChatInput
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Dispatch, SetStateAction } from 'preact/compat';
|
|
2
1
|
import { useRef } from 'preact/hooks';
|
|
3
2
|
import { GripVertical, Pencil, Send, X } from 'lucide-preact';
|
|
4
3
|
import type { QueuedMessage } from '@/shared/types';
|
|
@@ -7,7 +6,10 @@ export type { QueuedMessage };
|
|
|
7
6
|
|
|
8
7
|
interface QueueListProps {
|
|
9
8
|
queue: QueuedMessage[];
|
|
10
|
-
|
|
9
|
+
/** Reorder handler (drag-and-drop): receives the new id order. */
|
|
10
|
+
onReorder?: (orderedIds: string[]) => void;
|
|
11
|
+
/** Remove one row by id. */
|
|
12
|
+
onRemove?: (id: string) => void;
|
|
11
13
|
/** True when every row in this panel is a steering delivery. */
|
|
12
14
|
isSteering?: boolean;
|
|
13
15
|
onEdit?: (item: QueuedMessage) => void;
|
|
@@ -20,18 +22,18 @@ const KIND_LABEL: Record<string, string> = {
|
|
|
20
22
|
followup: 'follow-up',
|
|
21
23
|
};
|
|
22
24
|
|
|
23
|
-
export function QueueList({ queue,
|
|
25
|
+
export function QueueList({ queue, onReorder, onRemove, isSteering = false, onEdit, onSendNow }: QueueListProps) {
|
|
24
26
|
const dragItem = useRef<number | null>(null);
|
|
25
27
|
const dragOverItem = useRef<number | null>(null);
|
|
26
28
|
|
|
27
29
|
const handleSort = () => {
|
|
28
30
|
if (dragItem.current === null || dragOverItem.current === null) return;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
31
|
+
const ids = queue.map((i) => i.id);
|
|
32
|
+
const draggedId = ids.splice(dragItem.current, 1)[0];
|
|
33
|
+
ids.splice(dragOverItem.current, 0, draggedId);
|
|
32
34
|
dragItem.current = null;
|
|
33
35
|
dragOverItem.current = null;
|
|
34
|
-
|
|
36
|
+
onReorder?.(ids);
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
if (queue.length === 0) return null;
|
|
@@ -80,13 +82,15 @@ export function QueueList({ queue, setQueue, isSteering = false, onEdit, onSendN
|
|
|
80
82
|
<Pencil size={12} />
|
|
81
83
|
</button>
|
|
82
84
|
)}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
{onRemove && (
|
|
86
|
+
<button
|
|
87
|
+
onClick={() => onRemove(item.id)}
|
|
88
|
+
className="text-ink/40 hover:text-error opacity-0 group-hover:opacity-100 transition-opacity"
|
|
89
|
+
title="Remove"
|
|
90
|
+
>
|
|
91
|
+
<X size={14} />
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
90
94
|
</div>
|
|
91
95
|
))}
|
|
92
96
|
</div>
|
|
@@ -49,7 +49,8 @@ export function ChatTimeline({ className = '', appSettings = {}, onSessionTitle,
|
|
|
49
49
|
loadOlder,
|
|
50
50
|
generatingVerb,
|
|
51
51
|
messageQueue,
|
|
52
|
-
|
|
52
|
+
removeMessage,
|
|
53
|
+
reorderMessages,
|
|
53
54
|
steeringQueue,
|
|
54
55
|
setSteeringQueue,
|
|
55
56
|
inputValue,
|
|
@@ -243,7 +244,8 @@ export function ChatTimeline({ className = '', appSettings = {}, onSessionTitle,
|
|
|
243
244
|
provider={sessionProvider}
|
|
244
245
|
providerNames={providerNames}
|
|
245
246
|
messageQueue={messageQueue}
|
|
246
|
-
|
|
247
|
+
onRemoveQueueItem={removeMessage}
|
|
248
|
+
onReorderQueue={reorderMessages}
|
|
247
249
|
onEditQueueItem={handleEditQueueItem}
|
|
248
250
|
onSendNowQueueItem={handleSendNowQueueItem}
|
|
249
251
|
steeringQueue={steeringQueue}
|
|
@@ -113,7 +113,7 @@ export function GitBranchToolbar({
|
|
|
113
113
|
<button
|
|
114
114
|
type="button"
|
|
115
115
|
onClick={onSync}
|
|
116
|
-
disabled={isBusy
|
|
116
|
+
disabled={isBusy}
|
|
117
117
|
title={
|
|
118
118
|
syncCount.ahead > 0 && syncCount.behind > 0
|
|
119
119
|
? `Pull ${syncCount.behind} then push ${syncCount.ahead} commit(s)`
|
|
@@ -121,7 +121,7 @@ export function GitBranchToolbar({
|
|
|
121
121
|
? `Push ${syncCount.ahead} commit(s)`
|
|
122
122
|
: syncCount.behind > 0
|
|
123
123
|
? `Pull ${syncCount.behind} commit(s)`
|
|
124
|
-
: 'Up to date'
|
|
124
|
+
: 'Up to date — sync anyway'
|
|
125
125
|
}
|
|
126
126
|
className="flex items-center gap-1 p-1 rounded hover:text-ink hover:bg-ink/5 cursor-pointer transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
127
127
|
>
|
|
@@ -34,6 +34,10 @@ export function GitPanel({ className = '', enabled = true, rootPath, refreshKey
|
|
|
34
34
|
const params = new URLSearchParams();
|
|
35
35
|
if (rootPath) params.set('root', rootPath);
|
|
36
36
|
if (targetRepo && targetRepo !== '.') params.set('repo', targetRepo);
|
|
37
|
+
// The toolbar's ↑ahead ↓behind badge is what this asks for; it makes the
|
|
38
|
+
// loader refresh the branch's remote-tracking ref before counting, so the
|
|
39
|
+
// numbers describe origin rather than the last fetch.
|
|
40
|
+
params.set('sync', '1');
|
|
37
41
|
params.set('t', String(Date.now()));
|
|
38
42
|
fetcher.load(`/api/fs/git?${params.toString()}`);
|
|
39
43
|
};
|
|
@@ -200,7 +204,7 @@ export function GitPanel({ className = '', enabled = true, rootPath, refreshKey
|
|
|
200
204
|
if (!enabled || pollingRepos || rescanningRepos) return;
|
|
201
205
|
setExtraRepos(repos);
|
|
202
206
|
setRescanningRepos(true);
|
|
203
|
-
const params = new URLSearchParams({ rescan: '1' });
|
|
207
|
+
const params = new URLSearchParams({ rescan: '1', sync: '1' });
|
|
204
208
|
if (rootPath) params.set('root', rootPath);
|
|
205
209
|
if (activeRepo !== '.') params.set('repo', activeRepo);
|
|
206
210
|
params.set('t', String(Date.now()));
|
|
@@ -1,23 +1,46 @@
|
|
|
1
|
-
import { Loader2, Terminal } from 'lucide-preact';
|
|
1
|
+
import { Loader2, RotateCw, Terminal } from 'lucide-preact';
|
|
2
2
|
import { GitRepoDropdown } from '@/client/components/workspace/file-explorer/GitRepoDropdown';
|
|
3
|
+
import type { TerminalStatus } from '@/client/hooks/workspace/terminal';
|
|
3
4
|
|
|
4
5
|
interface TerminalHeaderProps {
|
|
5
|
-
|
|
6
|
+
status: TerminalStatus;
|
|
7
|
+
exitCode: number | null;
|
|
6
8
|
rootPath?: string;
|
|
7
9
|
activeRepo: string;
|
|
8
10
|
onSelectRepo: (repo: string) => void;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
/** Working directory the shell actually runs in. */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
shell?: string;
|
|
14
|
+
gitBranch?: string;
|
|
15
|
+
onRestart: () => void;
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
const STATUS_LABEL: Record<TerminalStatus, string> = {
|
|
19
|
+
connecting: 'CONNECTING',
|
|
20
|
+
running: 'LIVE',
|
|
21
|
+
exited: 'EXITED',
|
|
22
|
+
error: 'ERROR',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const STATUS_CLASS: Record<TerminalStatus, string> = {
|
|
26
|
+
connecting: 'bg-warning/10 text-warning border-warning/20',
|
|
27
|
+
running: 'bg-success/10 text-success border-success/20',
|
|
28
|
+
exited: 'bg-ink/5 text-ink/60 border-ink/15',
|
|
29
|
+
error: 'bg-error/10 text-error border-error/20',
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
export function TerminalHeader({
|
|
14
|
-
|
|
33
|
+
status,
|
|
34
|
+
exitCode,
|
|
15
35
|
rootPath,
|
|
16
36
|
activeRepo,
|
|
17
37
|
onSelectRepo,
|
|
18
|
-
|
|
19
|
-
|
|
38
|
+
cwd,
|
|
39
|
+
shell,
|
|
40
|
+
gitBranch,
|
|
41
|
+
onRestart,
|
|
20
42
|
}: TerminalHeaderProps) {
|
|
43
|
+
const shellName = shell ? shell.split('/').pop() : '';
|
|
21
44
|
return (
|
|
22
45
|
<div className="h-10 px-3 border-b border-ink/10 bg-paper flex items-center justify-between flex-shrink-0 select-none">
|
|
23
46
|
{/* Left cluster: Title, Status, Repo */}
|
|
@@ -29,22 +52,31 @@ export function TerminalHeader({
|
|
|
29
52
|
</span>
|
|
30
53
|
</div>
|
|
31
54
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
<span
|
|
56
|
+
className={`inline-flex items-center space-x-1 px-1.5 py-0.5 rounded text-[10px] font-mono border ${STATUS_CLASS[status]}`}
|
|
57
|
+
title={status === 'exited' && exitCode !== null ? `Shell exited with code ${exitCode}` : undefined}
|
|
58
|
+
>
|
|
59
|
+
{status === 'connecting' && <Loader2 size={10} className="animate-spin" />}
|
|
60
|
+
<span>{status === 'exited' && exitCode !== null ? `EXIT ${exitCode}` : STATUS_LABEL[status]}</span>
|
|
61
|
+
</span>
|
|
38
62
|
|
|
39
63
|
<GitRepoDropdown rootPath={rootPath} activeRepo={activeRepo} onSelectRepo={onSelectRepo} />
|
|
64
|
+
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={onRestart}
|
|
68
|
+
title="Kill this shell and start a new one"
|
|
69
|
+
className="p-1 rounded text-ink/50 hover:text-ink hover:bg-ink/5 transition-colors cursor-pointer flex-shrink-0"
|
|
70
|
+
>
|
|
71
|
+
<RotateCw size={12} />
|
|
72
|
+
</button>
|
|
40
73
|
</div>
|
|
41
74
|
|
|
42
|
-
|
|
43
|
-
<span className="
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)}
|
|
75
|
+
<span className="hidden sm:flex items-center space-x-2 text-[10px] font-mono text-ink/40 flex-shrink-0 min-w-0">
|
|
76
|
+
{cwd && <span className="truncate max-w-[280px]" title={cwd}>{cwd}</span>}
|
|
77
|
+
{gitBranch && <span>{gitBranch}</span>}
|
|
78
|
+
{shellName && <span>{shellName}</span>}
|
|
79
|
+
</span>
|
|
48
80
|
</div>
|
|
49
81
|
);
|
|
50
82
|
}
|