ompchamber 0.7.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.e880517fcd.css +2 -0
- package/dist/client/static/js/async/{3y.4c2596f64c.js → 3y.71f2ccf3b6.js} +2 -2
- package/dist/client/static/js/async/{xw.ae67ba06fd.js → 5b.0d6ae50fe8.js} +10 -10
- package/dist/client/static/js/async/5bh.8b2363424f.js +1 -0
- package/dist/client/static/js/async/{vy.bbe114c4e0.js → bi.09688ba75c.js} +1 -2
- package/dist/client/static/js/async/c.797d933882.js +1 -0
- package/dist/client/static/js/async/{dc.6dc4b0a04b.js → dc.856fda6560.js} +2 -2
- package/dist/client/static/js/async/g.a06ca3b3e9.js +2 -0
- package/dist/client/static/js/async/g.a06ca3b3e9.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{m.4b29d34f93.js → m.c038c71b9f.js} +2 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js +2 -0
- package/dist/client/static/js/async/ms.98960af4eb.js +67 -0
- package/dist/client/static/js/async/nam.b5663e919f.js +10 -0
- package/dist/client/static/js/async/nam.b5663e919f.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/ot.84c71b308f.js +2 -0
- package/dist/client/static/js/async/ot.84c71b308f.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{q.542c184fec.js → q.23af2a358f.js} +3 -3
- package/dist/client/static/js/async/{s.413c2a4683.js → s.222ee974f6.js} +2 -2
- package/dist/client/static/js/async/t.4dca0e2ba8.js +1 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
- package/dist/client/static/js/async/xn.6360379f71.js +2 -0
- package/dist/client/static/js/async/xnl.feb0021e50.js +2 -0
- package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +12 -0
- package/dist/client/static/js/index.0de15c5e17.js +33 -0
- package/dist/client/static/js/{uy.2e8929ed0c.js → jp.50817896f3.js} +6 -6
- package/package.json +2 -1
- package/src/client/components/common/MarkdownRenderer.tsx +69 -27
- package/src/client/components/common/code-editor/history.test.ts +118 -0
- package/src/client/components/common/code-editor/history.ts +101 -0
- package/src/client/components/common/code-editor/index.tsx +56 -66
- package/src/client/components/common/code-surface/index.tsx +84 -11
- package/src/client/components/common/code-surface/measure.ts +66 -17
- package/src/client/components/common/diagram-viewer/index.tsx +258 -0
- package/src/client/components/common/diagram-viewer/read-block.ts +53 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +2 -1
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +11 -4
- 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/settings/LazyModal.tsx +1 -1
- package/src/client/components/settings/Modal.tsx +2 -1
- 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/ToolCallCard.tsx +8 -1
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +11 -52
- package/src/client/components/workspace/chat-timeline/chat-input/selection.ts +57 -0
- package/src/client/components/workspace/chat-timeline/index.tsx +124 -108
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/FrameControl.tsx +172 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +155 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/drafts.ts +113 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +92 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Footer.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Header.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Lazy.tsx +8 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/SelectBody.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/index.tsx +16 -10
- package/src/client/components/workspace/chat-timeline/tool-renderers/index.tsx +2 -2
- package/src/client/components/workspace/file-explorer/index.tsx +18 -5
- 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 +23 -2
- package/src/client/hooks/chat/omp/session-sidebar-controller.ts +1 -1
- package/src/client/hooks/chat/timeline/actions.ts +23 -34
- package/src/client/hooks/chat/timeline/ask-frames.ts +102 -0
- package/src/client/hooks/chat/timeline/extension-dialog.ts +12 -9
- package/src/client/hooks/chat/timeline/index.ts +18 -28
- package/src/client/hooks/chat/timeline/prompt-text.ts +35 -0
- package/src/client/hooks/chat/timeline/queue.ts +110 -46
- package/src/client/hooks/chat/timeline/send.ts +13 -27
- package/src/client/hooks/chat/timeline/session-load.ts +48 -106
- package/src/client/hooks/chat/timeline/session-pagination.ts +144 -0
- package/src/client/hooks/editor/use-code-window.ts +118 -0
- package/src/client/hooks/ui/input-required-alert.ts +43 -0
- package/src/client/hooks/ui/notification-sound.ts +65 -0
- package/src/client/hooks/ui/theme.ts +22 -6
- package/src/client/hooks/workspace/terminal/index.ts +217 -0
- package/src/client/hooks/workspace/terminal/socket.ts +227 -0
- package/src/client/tailwind.css +38 -12
- 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 +72 -6
- package/src/server/lib/omp/rpc/session-commands.ts +6 -0
- package/src/server/lib/omp/rpc/session-registry.ts +18 -0
- package/src/server/lib/omp/session/sidebar-data.server.ts +10 -4
- 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/ask-questions.test.ts +169 -0
- package/src/shared/lib/chat/ask-questions.ts +264 -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/code/lazy-window.test.ts +161 -0
- package/src/shared/lib/code/lazy-window.ts +156 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +50 -1
- package/src/shared/lib/code/syntax-highlight.ts +14 -1
- package/src/shared/lib/code/windowed-highlight.ts +63 -0
- package/src/shared/lib/markdown/mermaid.ts +8 -6
- 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 +14 -2
- package/src/shared/types/terminal.ts +17 -15
- package/src/shared/types/workspace.ts +6 -2
- package/dist/client/static/css/index.51f68210a5.css +0 -2
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/c.cd45b0ac45.js +0 -1
- package/dist/client/static/js/async/fw.6a51998a8e.js +0 -6
- package/dist/client/static/js/async/g.86e2256a70.js +0 -2
- package/dist/client/static/js/async/kc.bbdb35b771.js +0 -1
- package/dist/client/static/js/async/mw.ca1512563c.js +0 -10
- package/dist/client/static/js/async/rf.d626afc367.js +0 -2
- package/dist/client/static/js/async/t.cda4891526.js +0 -1
- package/dist/client/static/js/async/uf.24ca3d49c3.js +0 -2
- package/dist/client/static/js/async/uf.24ca3d49c3.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/v0.394ed37c6b.js +0 -2
- package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn.afecebbbd9.js +0 -2
- package/dist/client/static/js/index.eccad0b70a.js +0 -33
- package/dist/client/static/js/index.eccad0b70a.js.LICENSE.txt +0 -4
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Ask.tsx +0 -205
- 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/{3y.4c2596f64c.js.LICENSE.txt → 3y.71f2ccf3b6.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.86e2256a70.js.LICENSE.txt → 5b.0d6ae50fe8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{dc.6dc4b0a04b.js.LICENSE.txt → dc.856fda6560.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{fw.6a51998a8e.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.4b29d34f93.js.LICENSE.txt → ma.2c261b04dd.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{xn.afecebbbd9.js.LICENSE.txt → ms.98960af4eb.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{mw.ca1512563c.js.LICENSE.txt → q.23af2a358f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.542c184fec.js.LICENSE.txt → s.222ee974f6.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rf.d626afc367.js.LICENSE.txt → uf.46ad6209fa.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.413c2a4683.js.LICENSE.txt → xn.6360379f71.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{async/xw.ae67ba06fd.js.LICENSE.txt → index.0de15c5e17.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{uy.2e8929ed0c.js.LICENSE.txt → jp.50817896f3.js.LICENSE.txt} +0 -0
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import type { TargetedKeyboardEvent } from 'preact';
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
3
|
-
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
4
|
-
import { useSessionStateContext } from '@/client/hooks/workspace/session-state/context';
|
|
5
|
-
import type { TerminalLogItem } from '@/shared/types';
|
|
6
|
-
|
|
7
|
-
const DEFAULT_COMMAND_HISTORY = ['bun --version', 'bun run build', 'git status -s'];
|
|
8
|
-
|
|
9
|
-
const MAX_TERMINAL_LOGS = 50;
|
|
10
|
-
|
|
11
|
-
function capLogs(items: TerminalLogItem[]): TerminalLogItem[] {
|
|
12
|
-
return items.length > MAX_TERMINAL_LOGS ? items.slice(-MAX_TERMINAL_LOGS) : items;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface UseTerminalOptions {
|
|
16
|
-
onStreamChunk?: (text: string) => void;
|
|
17
|
-
onCommandStart?: (cmd: string, options?: { fromXterm?: boolean }) => void;
|
|
18
|
-
onCommandEnd?: (exitCode: number, cwd: string) => void;
|
|
19
|
-
onClear?: () => void;
|
|
20
|
-
root?: string;
|
|
21
|
-
repo?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function useTerminal(options?: UseTerminalOptions) {
|
|
25
|
-
const [terminalLogs, setTerminalLogs] = useState<TerminalLogItem[]>([]);
|
|
26
|
-
const [terminalInput, setTerminalInput] = useSessionState<string>('terminal.input', '');
|
|
27
|
-
const [isRunning, setIsRunning] = useState(false);
|
|
28
|
-
const [cwd, setCwd] = useSessionState<string>('terminal.cwd', '.');
|
|
29
|
-
const [commandHistory, setCommandHistory] = useSessionState<string[]>('terminal.commandHistory', DEFAULT_COMMAND_HISTORY);
|
|
30
|
-
const [historyPointer, setHistoryPointer] = useState<number>(-1);
|
|
31
|
-
const [systemInfo, setSystemInfo] = useState<{
|
|
32
|
-
bunVersion: string;
|
|
33
|
-
nodeVersion: string;
|
|
34
|
-
gitBranch: string;
|
|
35
|
-
}>({
|
|
36
|
-
bunVersion: '',
|
|
37
|
-
nodeVersion: '',
|
|
38
|
-
gitBranch: 'main',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const abortControllerRef = useRef<AbortController | null>(null);
|
|
42
|
-
const streamCallbackRef = useRef(options?.onStreamChunk);
|
|
43
|
-
streamCallbackRef.current = options?.onStreamChunk;
|
|
44
|
-
|
|
45
|
-
const commandStartRef = useRef(options?.onCommandStart);
|
|
46
|
-
commandStartRef.current = options?.onCommandStart;
|
|
47
|
-
|
|
48
|
-
const commandEndRef = useRef(options?.onCommandEnd);
|
|
49
|
-
commandEndRef.current = options?.onCommandEnd;
|
|
50
|
-
|
|
51
|
-
const clearCallbackRef = useRef(options?.onClear);
|
|
52
|
-
clearCallbackRef.current = options?.onClear;
|
|
53
|
-
|
|
54
|
-
const rootRef = useRef(options?.root);
|
|
55
|
-
rootRef.current = options?.root;
|
|
56
|
-
const prevRootRef = useRef(options?.root);
|
|
57
|
-
|
|
58
|
-
const repoRef = useRef(options?.repo);
|
|
59
|
-
repoRef.current = options?.repo;
|
|
60
|
-
const prevRepoRef = useRef(options?.repo);
|
|
61
|
-
|
|
62
|
-
const { sessionId, ready } = useSessionStateContext();
|
|
63
|
-
const sessionIdRef = useRef(sessionId);
|
|
64
|
-
const absorbRestoreRef = useRef(true);
|
|
65
|
-
|
|
66
|
-
// Absorb the repo/cwd prop changes produced by a session restore (skip the
|
|
67
|
-
// reset for one tick after the blob loads), while genuine repo changes on a
|
|
68
|
-
// settled session still clear cwd/logs.
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (sessionIdRef.current !== sessionId) {
|
|
71
|
-
sessionIdRef.current = sessionId;
|
|
72
|
-
absorbRestoreRef.current = true;
|
|
73
|
-
}
|
|
74
|
-
if (!ready || !absorbRestoreRef.current) return;
|
|
75
|
-
const timer = setTimeout(() => {
|
|
76
|
-
absorbRestoreRef.current = false;
|
|
77
|
-
}, 0);
|
|
78
|
-
return () => clearTimeout(timer);
|
|
79
|
-
}, [sessionId, ready]);
|
|
80
|
-
|
|
81
|
-
// Fetch real system environment on mount / when the scoped root or repo changes
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
const root = options?.root;
|
|
84
|
-
const repo = options?.repo;
|
|
85
|
-
if (prevRootRef.current !== root || prevRepoRef.current !== repo) {
|
|
86
|
-
prevRootRef.current = root;
|
|
87
|
-
prevRepoRef.current = repo;
|
|
88
|
-
if (!absorbRestoreRef.current) {
|
|
89
|
-
setCwd('.');
|
|
90
|
-
setTerminalLogs([]);
|
|
91
|
-
clearCallbackRef.current?.();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const rootQuery = root ? `?root=${encodeURIComponent(root)}` : '';
|
|
95
|
-
const repoQuery = repo && repo !== '.' ? `&repo=${encodeURIComponent(repo)}` : '';
|
|
96
|
-
fetch(`/api/terminal/run${rootQuery}${repoQuery}`)
|
|
97
|
-
.then(res => res.json())
|
|
98
|
-
.then(data => {
|
|
99
|
-
if (data && data.bunVersion) {
|
|
100
|
-
setSystemInfo({
|
|
101
|
-
bunVersion: data.bunVersion ?? '',
|
|
102
|
-
nodeVersion: data.nodeVersion ?? '',
|
|
103
|
-
gitBranch: data.gitBranch || 'main',
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
.catch(() => {});
|
|
108
|
-
}, [options?.root, options?.repo]);
|
|
109
|
-
|
|
110
|
-
const clearLogs = useCallback(() => {
|
|
111
|
-
setTerminalLogs([]);
|
|
112
|
-
}, []);
|
|
113
|
-
|
|
114
|
-
const executeCommand = useCallback(
|
|
115
|
-
async (customCommand?: string, options?: { fromXterm?: boolean }) => {
|
|
116
|
-
const rawCmd = (customCommand !== undefined ? customCommand : terminalInput).trim();
|
|
117
|
-
if (!rawCmd || isRunning) return;
|
|
118
|
-
|
|
119
|
-
// Handle clear command locally
|
|
120
|
-
if (rawCmd === 'clear' || rawCmd === 'cls') {
|
|
121
|
-
setTerminalLogs([]);
|
|
122
|
-
setTerminalInput('');
|
|
123
|
-
setHistoryPointer(-1);
|
|
124
|
-
clearCallbackRef.current?.();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const commandId = `cmd-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
|
|
129
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
130
|
-
|
|
131
|
-
// Add to history
|
|
132
|
-
setCommandHistory(prev => (prev[prev.length - 1] === rawCmd ? prev : [...prev, rawCmd]));
|
|
133
|
-
setHistoryPointer(-1);
|
|
134
|
-
setTerminalInput('');
|
|
135
|
-
setIsRunning(true);
|
|
136
|
-
|
|
137
|
-
// Notify start to xterm
|
|
138
|
-
commandStartRef.current?.(rawCmd, options);
|
|
139
|
-
|
|
140
|
-
const controller = new AbortController();
|
|
141
|
-
abortControllerRef.current = controller;
|
|
142
|
-
|
|
143
|
-
let accumulatedStdout = '';
|
|
144
|
-
let exitCode = 0;
|
|
145
|
-
let nextCwd = cwd;
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const rootQuery = rootRef.current ? `&root=${encodeURIComponent(rootRef.current)}` : '';
|
|
149
|
-
const repoQuery = repoRef.current && repoRef.current !== '.' ? `&repo=${encodeURIComponent(repoRef.current)}` : '';
|
|
150
|
-
const streamUrl = `/api/terminal/stream?cmd=${encodeURIComponent(rawCmd)}&cwd=${encodeURIComponent(cwd)}${rootQuery}${repoQuery}`;
|
|
151
|
-
const response = await fetch(streamUrl, {
|
|
152
|
-
signal: controller.signal,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (!response.body) {
|
|
156
|
-
throw new Error('Streaming not supported by browser');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const reader = response.body.getReader();
|
|
160
|
-
const decoder = new TextDecoder();
|
|
161
|
-
let buffer = '';
|
|
162
|
-
|
|
163
|
-
while (true) {
|
|
164
|
-
const { value, done } = await reader.read();
|
|
165
|
-
if (done) break;
|
|
166
|
-
|
|
167
|
-
buffer += decoder.decode(value, { stream: true });
|
|
168
|
-
const lines = buffer.split('\n');
|
|
169
|
-
buffer = lines.pop() || '';
|
|
170
|
-
|
|
171
|
-
let currentEvent = 'message';
|
|
172
|
-
for (const line of lines) {
|
|
173
|
-
const trimmed = line.trim();
|
|
174
|
-
if (trimmed.startsWith('event:')) {
|
|
175
|
-
currentEvent = trimmed.slice(6).trim();
|
|
176
|
-
} else if (trimmed.startsWith('data:')) {
|
|
177
|
-
const dataStr = trimmed.slice(5).trim();
|
|
178
|
-
try {
|
|
179
|
-
const payload = JSON.parse(dataStr);
|
|
180
|
-
if (currentEvent === 'data' && payload.text) {
|
|
181
|
-
const normalizedText = payload.text.replace(/\r?\n/g, '\r\n');
|
|
182
|
-
accumulatedStdout += normalizedText;
|
|
183
|
-
streamCallbackRef.current?.(normalizedText);
|
|
184
|
-
} else if (currentEvent === 'exit') {
|
|
185
|
-
exitCode = typeof payload.exitCode === 'number' ? payload.exitCode : 0;
|
|
186
|
-
if (payload.cwd && payload.cwd !== cwd) {
|
|
187
|
-
nextCwd = payload.cwd;
|
|
188
|
-
setCwd(payload.cwd);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} catch {}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
} catch (err: any) {
|
|
196
|
-
if (err.name === 'AbortError') {
|
|
197
|
-
exitCode = 130;
|
|
198
|
-
const abortMsg = '\r\n^C (Interrupted)\r\n';
|
|
199
|
-
accumulatedStdout += abortMsg;
|
|
200
|
-
streamCallbackRef.current?.(abortMsg);
|
|
201
|
-
} else {
|
|
202
|
-
exitCode = 1;
|
|
203
|
-
const errMsg = `\r\n\x1b[31mError: ${err.message || 'Execution failed'}\x1b[0m\r\n`;
|
|
204
|
-
accumulatedStdout += errMsg;
|
|
205
|
-
streamCallbackRef.current?.(errMsg);
|
|
206
|
-
}
|
|
207
|
-
} finally {
|
|
208
|
-
setIsRunning(false);
|
|
209
|
-
abortControllerRef.current = null;
|
|
210
|
-
commandEndRef.current?.(exitCode, nextCwd);
|
|
211
|
-
|
|
212
|
-
setTerminalLogs(prev =>
|
|
213
|
-
capLogs([
|
|
214
|
-
...prev,
|
|
215
|
-
{
|
|
216
|
-
id: commandId,
|
|
217
|
-
command: rawCmd,
|
|
218
|
-
stdout: accumulatedStdout,
|
|
219
|
-
exitCode,
|
|
220
|
-
timestamp,
|
|
221
|
-
cwd: nextCwd,
|
|
222
|
-
},
|
|
223
|
-
])
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
[terminalInput, isRunning, cwd, sessionId]
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
const cancelRunningCommand = useCallback(() => {
|
|
231
|
-
if (abortControllerRef.current) {
|
|
232
|
-
abortControllerRef.current.abort();
|
|
233
|
-
}
|
|
234
|
-
}, []);
|
|
235
|
-
|
|
236
|
-
const handleKeyDown = useCallback(
|
|
237
|
-
(e: TargetedKeyboardEvent<HTMLInputElement>) => {
|
|
238
|
-
if (e.key === 'ArrowUp') {
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
if (commandHistory.length === 0) return;
|
|
241
|
-
setHistoryPointer(prev => {
|
|
242
|
-
const next = prev === -1 ? commandHistory.length - 1 : Math.max(0, prev - 1);
|
|
243
|
-
setTerminalInput(commandHistory[next] || '');
|
|
244
|
-
return next;
|
|
245
|
-
});
|
|
246
|
-
} else if (e.key === 'ArrowDown') {
|
|
247
|
-
e.preventDefault();
|
|
248
|
-
if (historyPointer === -1) return;
|
|
249
|
-
setHistoryPointer(prev => {
|
|
250
|
-
const next = prev + 1;
|
|
251
|
-
if (next >= commandHistory.length) {
|
|
252
|
-
setTerminalInput('');
|
|
253
|
-
return -1;
|
|
254
|
-
}
|
|
255
|
-
setTerminalInput(commandHistory[next] || '');
|
|
256
|
-
return next;
|
|
257
|
-
});
|
|
258
|
-
} else if (e.key === 'c' && e.ctrlKey) {
|
|
259
|
-
if (isRunning) {
|
|
260
|
-
cancelRunningCommand();
|
|
261
|
-
}
|
|
262
|
-
} else if (e.key === 'l' && e.ctrlKey) {
|
|
263
|
-
e.preventDefault();
|
|
264
|
-
clearLogs();
|
|
265
|
-
clearCallbackRef.current?.();
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
[commandHistory, historyPointer, isRunning, cancelRunningCommand, clearLogs, sessionId]
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
terminalLogs,
|
|
273
|
-
terminalInput,
|
|
274
|
-
setTerminalInput,
|
|
275
|
-
isRunning,
|
|
276
|
-
cwd,
|
|
277
|
-
systemInfo,
|
|
278
|
-
executeCommand,
|
|
279
|
-
clearLogs,
|
|
280
|
-
cancelRunningCommand,
|
|
281
|
-
handleKeyDown,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { isWithinRoot } from '@/server/lib/fs/root';
|
|
3
|
-
|
|
4
|
-
/** True when `dir` resolves to an existing directory (async stat probe). */
|
|
5
|
-
export async function isDirectory(dir: string): Promise<boolean> {
|
|
6
|
-
const stat = await Bun.file(dir).stat().catch(() => null);
|
|
7
|
-
return stat?.isDirectory() ?? false;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Resolve the directory a terminal command should run in. `requestedCwd` is
|
|
12
|
-
* honored only when it stays within `rootDir` and names a real directory;
|
|
13
|
-
* otherwise the command runs at the root.
|
|
14
|
-
*/
|
|
15
|
-
export async function resolveTerminalCwd(rootDir: string, requestedCwd: string): Promise<string> {
|
|
16
|
-
if (!requestedCwd) return rootDir;
|
|
17
|
-
const resolved = path.isAbsolute(requestedCwd)
|
|
18
|
-
? path.resolve(requestedCwd)
|
|
19
|
-
: path.resolve(rootDir, requestedCwd);
|
|
20
|
-
if (isWithinRoot(rootDir, resolved) && (await isDirectory(resolved))) return resolved;
|
|
21
|
-
return rootDir;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type CdResult =
|
|
25
|
-
| { ok: true; cwd: string }
|
|
26
|
-
| { ok: false; reason: 'above-root' | 'missing' };
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve a `cd <target>` against the current directory. The containment test
|
|
30
|
-
* mirrors the original `startsWith(rootDir)` (no separator) exactly, so an
|
|
31
|
-
* out-of-root sibling is still rejected the same way it always was.
|
|
32
|
-
*/
|
|
33
|
-
export async function resolveCdTarget(
|
|
34
|
-
rootDir: string,
|
|
35
|
-
currentDir: string,
|
|
36
|
-
target: string,
|
|
37
|
-
): Promise<CdResult> {
|
|
38
|
-
const nextDir = path.resolve(currentDir, target);
|
|
39
|
-
if (!nextDir.startsWith(rootDir)) return { ok: false, reason: 'above-root' };
|
|
40
|
-
if (!(await isDirectory(nextDir))) return { ok: false, reason: 'missing' };
|
|
41
|
-
return { ok: true, cwd: path.relative(rootDir, nextDir) || '.' };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Match a bare `cd` invocation; returns the trimmed target or null. */
|
|
45
|
-
export function matchCdCommand(command: string): string | null {
|
|
46
|
-
const match = command.match(/^cd(?:\s+(.*))?$/);
|
|
47
|
-
if (!match) return null;
|
|
48
|
-
return (match[1] || '').trim();
|
|
49
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { type LoaderFunctionArgs } from '@/server/lib/remix-compat';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { resolveRoot } from '@/server/lib/fs/root';
|
|
4
|
-
import { scopeToRepo } from '@/server/lib/fs/repo-scope';
|
|
5
|
-
import { createSseStream } from '@/server/lib/sse';
|
|
6
|
-
import { matchCdCommand, resolveCdTarget, resolveTerminalCwd } from '@/server/lib/fs/terminal-cwd';
|
|
7
|
-
|
|
8
|
-
export async function loader({ request }: LoaderFunctionArgs) {
|
|
9
|
-
const url = new URL(request.url);
|
|
10
|
-
const command = (url.searchParams.get('cmd') || '').trim();
|
|
11
|
-
const requestedCwd = (url.searchParams.get('cwd') || '').trim();
|
|
12
|
-
const baseDir = await resolveRoot(url.searchParams.get('root'), process.cwd());
|
|
13
|
-
const rootDir = await scopeToRepo(baseDir, url.searchParams.get('repo'));
|
|
14
|
-
const currentDir = await resolveTerminalCwd(rootDir, requestedCwd);
|
|
15
|
-
|
|
16
|
-
const stream = createSseStream({
|
|
17
|
-
headers: { 'Cache-Control': 'no-cache, no-transform' },
|
|
18
|
-
async onStart(handlers) {
|
|
19
|
-
if (!command) {
|
|
20
|
-
handlers.send('exit', { exitCode: 0, cwd: path.relative(rootDir, currentDir) || '.' });
|
|
21
|
-
handlers.close();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Special handling for cd navigation
|
|
26
|
-
const cdTarget = matchCdCommand(command);
|
|
27
|
-
if (cdTarget !== null) {
|
|
28
|
-
if (!cdTarget || cdTarget === '~' || cdTarget === '/') {
|
|
29
|
-
handlers.send('exit', { exitCode: 0, cwd: '.' });
|
|
30
|
-
handlers.close();
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const cd = await resolveCdTarget(rootDir, currentDir, cdTarget);
|
|
34
|
-
if (!cd.ok) {
|
|
35
|
-
handlers.send('data', {
|
|
36
|
-
text: cd.reason === 'above-root'
|
|
37
|
-
? `\x1b[31mcd: permission denied: cannot navigate above workspace root\x1b[0m\r\n`
|
|
38
|
-
: `\x1b[31mcd: no such file or directory: ${cdTarget}\x1b[0m\r\n`,
|
|
39
|
-
});
|
|
40
|
-
handlers.send('exit', { exitCode: 1, cwd: path.relative(rootDir, currentDir) || '.' });
|
|
41
|
-
handlers.close();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
handlers.send('exit', { exitCode: 0, cwd: cd.cwd });
|
|
45
|
-
handlers.close();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Spawn via /bin/sh so shell syntax (pipes, &&) keeps working, with full
|
|
50
|
-
// ANSI color support for the xterm viewer.
|
|
51
|
-
const proc = Bun.spawn({
|
|
52
|
-
cmd: ['sh', '-c', command],
|
|
53
|
-
cwd: currentDir,
|
|
54
|
-
stdin: 'ignore',
|
|
55
|
-
stdout: 'pipe',
|
|
56
|
-
stderr: 'pipe',
|
|
57
|
-
env: {
|
|
58
|
-
...Bun.env,
|
|
59
|
-
FORCE_COLOR: '1',
|
|
60
|
-
TERM: 'xterm-256color',
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
request.signal.addEventListener('abort', () => {
|
|
65
|
-
try {
|
|
66
|
-
proc.kill('SIGINT');
|
|
67
|
-
} catch {}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const decoder = new TextDecoder();
|
|
71
|
-
const pump = async (stream: ReadableStream<Uint8Array> | undefined) => {
|
|
72
|
-
if (!stream) return;
|
|
73
|
-
const reader = stream.getReader();
|
|
74
|
-
try {
|
|
75
|
-
for (;;) {
|
|
76
|
-
const { done, value } = await reader.read();
|
|
77
|
-
if (done) break;
|
|
78
|
-
handlers.send('data', { text: decoder.decode(value, { stream: true }) });
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
// Stream died with the process; exit handling below reports it.
|
|
82
|
-
} finally {
|
|
83
|
-
reader.releaseLock();
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const stdoutDone = pump(proc.stdout);
|
|
87
|
-
const stderrDone = pump(proc.stderr);
|
|
88
|
-
|
|
89
|
-
void Promise.all([stdoutDone, stderrDone, proc.exited]).then(([_, __, code]) => {
|
|
90
|
-
handlers.send('exit', {
|
|
91
|
-
exitCode: code ?? 0,
|
|
92
|
-
cwd: path.relative(rootDir, currentDir) || '.',
|
|
93
|
-
});
|
|
94
|
-
handlers.close();
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return stream.response;
|
|
100
|
-
}
|
/package/dist/client/static/js/async/{3y.4c2596f64c.js.LICENSE.txt → 3y.71f2ccf3b6.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{g.86e2256a70.js.LICENSE.txt → 5b.0d6ae50fe8.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{dc.6dc4b0a04b.js.LICENSE.txt → dc.856fda6560.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{fw.6a51998a8e.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{m.4b29d34f93.js.LICENSE.txt → ma.2c261b04dd.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{xn.afecebbbd9.js.LICENSE.txt → ms.98960af4eb.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{mw.ca1512563c.js.LICENSE.txt → q.23af2a358f.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{q.542c184fec.js.LICENSE.txt → s.222ee974f6.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{rf.d626afc367.js.LICENSE.txt → uf.46ad6209fa.js.LICENSE.txt}
RENAMED
|
File without changes
|
/package/dist/client/static/js/async/{s.413c2a4683.js.LICENSE.txt → xn.6360379f71.js.LICENSE.txt}
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/client/static/js/{uy.2e8929ed0c.js.LICENSE.txt → jp.50817896f3.js.LICENSE.txt}
RENAMED
|
File without changes
|