@tangle-network/sandbox-ui 0.10.7 → 0.10.8
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/chat.js +3 -3
- package/dist/chunk-4CLN43XT.js +45 -0
- package/dist/{chunk-HEXQVHXJ.js → chunk-5OQ27N57.js} +1 -1
- package/dist/chunk-DPGIXDAI.js +220 -0
- package/dist/{chunk-5UM2XMEJ.js → chunk-ESVYBDGA.js} +77 -20
- package/dist/{chunk-QOL4ZB24.js → chunk-MQXABZTB.js} +1 -1
- package/dist/{chunk-WKSGQVLI.js → chunk-XLG757B6.js} +1 -1
- package/dist/globals.css +6 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +7 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -7
- package/dist/pages.js +38 -5
- package/dist/run.js +2 -2
- package/dist/styles.css +6 -0
- package/dist/terminal.js +1 -1
- package/dist/use-sandbox-metrics-B64diPqN.d.ts +141 -0
- package/dist/utils.d.ts +17 -1
- package/dist/utils.js +5 -1
- package/dist/workspace.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-HRMUF35V.js +0 -19
- package/dist/chunk-S7OXQTST.js +0 -99
- package/dist/use-pty-session-0AOuwXgq.d.ts +0 -71
package/dist/chat.js
CHANGED
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
MessageList,
|
|
9
9
|
ThinkingIndicator,
|
|
10
10
|
UserMessage
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XLG757B6.js";
|
|
12
12
|
import "./chunk-54SQQMMM.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-MQXABZTB.js";
|
|
14
|
+
import "./chunk-4CLN43XT.js";
|
|
15
15
|
import "./chunk-MT5FJ3ZT.js";
|
|
16
16
|
import "./chunk-BX6AQMUS.js";
|
|
17
17
|
import "./chunk-ZNCEM5CD.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/utils/format.ts
|
|
2
|
+
function formatDuration(ms) {
|
|
3
|
+
if (ms < 1e3) return "<1s";
|
|
4
|
+
const seconds = Math.floor(ms / 1e3);
|
|
5
|
+
if (seconds < 60) return `${seconds}s`;
|
|
6
|
+
const minutes = Math.floor(seconds / 60);
|
|
7
|
+
const remaining = seconds % 60;
|
|
8
|
+
return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`;
|
|
9
|
+
}
|
|
10
|
+
function truncateText(text, max) {
|
|
11
|
+
const cleaned = text.replace(/\s+/g, " ").trim();
|
|
12
|
+
if (cleaned.length <= max) return cleaned;
|
|
13
|
+
return cleaned.slice(0, max).trim() + "...";
|
|
14
|
+
}
|
|
15
|
+
function formatUptime(ms) {
|
|
16
|
+
if (!Number.isFinite(ms) || ms < 0) return "\u2014";
|
|
17
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
18
|
+
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
19
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
20
|
+
const seconds = totalSeconds % 60;
|
|
21
|
+
if (minutes < 60) return `${minutes}m ${seconds}s`;
|
|
22
|
+
const hours = Math.floor(minutes / 60);
|
|
23
|
+
const remMinutes = minutes % 60;
|
|
24
|
+
if (hours < 24) return `${hours}h ${remMinutes}m`;
|
|
25
|
+
const days = Math.floor(hours / 24);
|
|
26
|
+
const remHours = hours % 24;
|
|
27
|
+
return `${days}d ${remHours}h`;
|
|
28
|
+
}
|
|
29
|
+
function formatBytes(bytes) {
|
|
30
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
|
|
31
|
+
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
|
32
|
+
const kb = bytes / 1024;
|
|
33
|
+
if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`;
|
|
34
|
+
const mb = kb / 1024;
|
|
35
|
+
if (mb < 1024) return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)} MB`;
|
|
36
|
+
const gb = mb / 1024;
|
|
37
|
+
return `${gb < 10 ? gb.toFixed(2) : gb.toFixed(1)} GB`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
formatDuration,
|
|
42
|
+
truncateText,
|
|
43
|
+
formatUptime,
|
|
44
|
+
formatBytes
|
|
45
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/hooks/use-auth.ts
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
function useAuth({
|
|
4
|
+
apiBaseUrl,
|
|
5
|
+
revalidateOnFocus = false,
|
|
6
|
+
shouldRetryOnError = false
|
|
7
|
+
}) {
|
|
8
|
+
const [user, setUser] = React.useState(null);
|
|
9
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
10
|
+
const [error, setError] = React.useState(null);
|
|
11
|
+
const retryTimerRef = React.useRef(null);
|
|
12
|
+
const abortRef = React.useRef(null);
|
|
13
|
+
const fetchSession = React.useCallback(async () => {
|
|
14
|
+
abortRef.current?.abort();
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
abortRef.current = controller;
|
|
17
|
+
setIsLoading(true);
|
|
18
|
+
setError(null);
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${apiBaseUrl}/auth/session`, {
|
|
21
|
+
credentials: "include",
|
|
22
|
+
signal: controller.signal
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
throw new Error("Not authenticated");
|
|
26
|
+
}
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (data.success && data.data) {
|
|
29
|
+
setUser(data.data);
|
|
30
|
+
} else {
|
|
31
|
+
setUser(null);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err.name === "AbortError") return;
|
|
35
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
36
|
+
setUser(null);
|
|
37
|
+
if (shouldRetryOnError) {
|
|
38
|
+
if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
|
|
39
|
+
retryTimerRef.current = setTimeout(fetchSession, 5e3);
|
|
40
|
+
}
|
|
41
|
+
} finally {
|
|
42
|
+
if (!controller.signal.aborted) {
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, [apiBaseUrl, shouldRetryOnError]);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
fetchSession();
|
|
49
|
+
return () => {
|
|
50
|
+
abortRef.current?.abort();
|
|
51
|
+
if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
|
|
52
|
+
};
|
|
53
|
+
}, [fetchSession]);
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (!revalidateOnFocus) return;
|
|
56
|
+
const handleFocus = () => {
|
|
57
|
+
fetchSession();
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("focus", handleFocus);
|
|
60
|
+
return () => window.removeEventListener("focus", handleFocus);
|
|
61
|
+
}, [revalidateOnFocus, fetchSession]);
|
|
62
|
+
return {
|
|
63
|
+
user,
|
|
64
|
+
isLoading,
|
|
65
|
+
isError: !!error,
|
|
66
|
+
error,
|
|
67
|
+
mutate: fetchSession
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function createAuthFetcher(_apiBaseUrl) {
|
|
71
|
+
return async function authFetcher(url, options) {
|
|
72
|
+
const res = await fetch(url, {
|
|
73
|
+
...options,
|
|
74
|
+
credentials: "include",
|
|
75
|
+
headers: {
|
|
76
|
+
...options?.headers
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
81
|
+
}
|
|
82
|
+
return res.json();
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function useApiKey() {
|
|
86
|
+
const [apiKey, setApiKey] = React.useState(null);
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
if (typeof window !== "undefined") {
|
|
89
|
+
setApiKey(localStorage.getItem("apiKey"));
|
|
90
|
+
}
|
|
91
|
+
}, []);
|
|
92
|
+
return apiKey;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/hooks/use-live-time.ts
|
|
96
|
+
import * as React2 from "react";
|
|
97
|
+
function useLiveTime(intervalMs = 1e3) {
|
|
98
|
+
const [now, setNow] = React2.useState(() => Date.now());
|
|
99
|
+
React2.useEffect(() => {
|
|
100
|
+
const requested = Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 1e3;
|
|
101
|
+
const delay = Math.max(requested, 100);
|
|
102
|
+
const id = window.setInterval(() => {
|
|
103
|
+
setNow(Date.now());
|
|
104
|
+
}, delay);
|
|
105
|
+
return () => {
|
|
106
|
+
window.clearInterval(id);
|
|
107
|
+
};
|
|
108
|
+
}, [intervalMs]);
|
|
109
|
+
return now;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/hooks/use-sandbox-metrics.ts
|
|
113
|
+
import * as React3 from "react";
|
|
114
|
+
function useSandboxMetrics({
|
|
115
|
+
apiBaseUrl,
|
|
116
|
+
sandboxId,
|
|
117
|
+
token,
|
|
118
|
+
enabled = true,
|
|
119
|
+
intervalMs = 3e3
|
|
120
|
+
}) {
|
|
121
|
+
const [metrics, setMetrics] = React3.useState(null);
|
|
122
|
+
const [loading, setLoading] = React3.useState(false);
|
|
123
|
+
const [error, setError] = React3.useState(null);
|
|
124
|
+
const [lastUpdatedAt, setLastUpdatedAt] = React3.useState(null);
|
|
125
|
+
const sampleRef = React3.useRef(null);
|
|
126
|
+
const hasLoadedRef = React3.useRef(false);
|
|
127
|
+
const prevSandboxIdRef = React3.useRef(null);
|
|
128
|
+
React3.useEffect(() => {
|
|
129
|
+
const sandboxCleared = !sandboxId || !apiBaseUrl;
|
|
130
|
+
const sandboxChanged = prevSandboxIdRef.current !== null && prevSandboxIdRef.current !== sandboxId;
|
|
131
|
+
if (sandboxCleared && prevSandboxIdRef.current !== null || sandboxChanged) {
|
|
132
|
+
sampleRef.current = null;
|
|
133
|
+
hasLoadedRef.current = false;
|
|
134
|
+
setMetrics(null);
|
|
135
|
+
setLastUpdatedAt(null);
|
|
136
|
+
setError(null);
|
|
137
|
+
if (sandboxCleared) setLoading(false);
|
|
138
|
+
}
|
|
139
|
+
prevSandboxIdRef.current = sandboxId ?? null;
|
|
140
|
+
if (!enabled || sandboxCleared) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
let cancelled = false;
|
|
145
|
+
let timeoutId = null;
|
|
146
|
+
const delay = Math.max(intervalMs, 500);
|
|
147
|
+
const fetchOnce = async () => {
|
|
148
|
+
if (!hasLoadedRef.current) setLoading(true);
|
|
149
|
+
try {
|
|
150
|
+
const headers = {};
|
|
151
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
152
|
+
const res = await fetch(
|
|
153
|
+
`${apiBaseUrl}/v1/sidecar-proxy/${encodeURIComponent(sandboxId)}/metrics/json`,
|
|
154
|
+
{
|
|
155
|
+
method: "GET",
|
|
156
|
+
credentials: "include",
|
|
157
|
+
headers,
|
|
158
|
+
signal: controller.signal
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
throw new Error(`Metrics request failed (HTTP ${res.status})`);
|
|
163
|
+
}
|
|
164
|
+
const data = await res.json();
|
|
165
|
+
const user = data?.process?.cpuSeconds?.user ?? 0;
|
|
166
|
+
const system = data?.process?.cpuSeconds?.system ?? 0;
|
|
167
|
+
const cpuSeconds = user + system;
|
|
168
|
+
const wallMs = Date.now();
|
|
169
|
+
if (cancelled) return;
|
|
170
|
+
let cpuPercent = null;
|
|
171
|
+
const prev = sampleRef.current;
|
|
172
|
+
if (prev && prev.sandboxId === sandboxId) {
|
|
173
|
+
const dCpu = cpuSeconds - prev.cpuSeconds;
|
|
174
|
+
const dWallSec = (wallMs - prev.wallMs) / 1e3;
|
|
175
|
+
if (dWallSec > 0 && dCpu >= 0) {
|
|
176
|
+
cpuPercent = dCpu / dWallSec * 100;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
sampleRef.current = { cpuSeconds, wallMs, sandboxId };
|
|
180
|
+
setMetrics({
|
|
181
|
+
cpuPercent,
|
|
182
|
+
rssBytes: data?.process?.memoryBytes?.rss ?? 0,
|
|
183
|
+
heapUsedBytes: data?.process?.memoryBytes?.heapUsed ?? 0,
|
|
184
|
+
heapTotalBytes: data?.process?.memoryBytes?.heapTotal ?? 0
|
|
185
|
+
});
|
|
186
|
+
setLastUpdatedAt(wallMs);
|
|
187
|
+
setError(null);
|
|
188
|
+
hasLoadedRef.current = true;
|
|
189
|
+
setLoading(false);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (cancelled || err instanceof DOMException && err.name === "AbortError") {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
195
|
+
if (!hasLoadedRef.current) setLoading(false);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const runLoop = async () => {
|
|
199
|
+
if (cancelled) return;
|
|
200
|
+
await fetchOnce();
|
|
201
|
+
if (cancelled) return;
|
|
202
|
+
timeoutId = window.setTimeout(runLoop, delay);
|
|
203
|
+
};
|
|
204
|
+
runLoop();
|
|
205
|
+
return () => {
|
|
206
|
+
cancelled = true;
|
|
207
|
+
controller.abort();
|
|
208
|
+
if (timeoutId !== null) window.clearTimeout(timeoutId);
|
|
209
|
+
};
|
|
210
|
+
}, [apiBaseUrl, sandboxId, token, enabled, intervalMs]);
|
|
211
|
+
return { metrics, loading, error, lastUpdatedAt };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export {
|
|
215
|
+
useAuth,
|
|
216
|
+
createAuthFetcher,
|
|
217
|
+
useApiKey,
|
|
218
|
+
useLiveTime,
|
|
219
|
+
useSandboxMetrics
|
|
220
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// src/hooks/use-pty-session.ts
|
|
2
2
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
function createEmptyBatch() {
|
|
4
|
+
return { data: "", waiters: [] };
|
|
5
|
+
}
|
|
3
6
|
function usePtySession({ apiUrl, token, onData }) {
|
|
4
7
|
const [isConnected, setIsConnected] = useState(false);
|
|
5
8
|
const [error, setError] = useState(null);
|
|
@@ -10,6 +13,20 @@ function usePtySession({ apiUrl, token, onData }) {
|
|
|
10
13
|
const mountedRef = useRef(true);
|
|
11
14
|
const onDataRef = useRef(onData);
|
|
12
15
|
const connectStreamRef = useRef(null);
|
|
16
|
+
const pendingBatchRef = useRef(createEmptyBatch());
|
|
17
|
+
const drainPromiseRef = useRef(null);
|
|
18
|
+
const inputAbortRef = useRef(null);
|
|
19
|
+
const ensureDrainRunningRef = useRef(null);
|
|
20
|
+
const rejectPendingInput = useCallback((reason) => {
|
|
21
|
+
const batch = pendingBatchRef.current;
|
|
22
|
+
if (batch.waiters.length === 0) {
|
|
23
|
+
pendingBatchRef.current = createEmptyBatch();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
pendingBatchRef.current = createEmptyBatch();
|
|
27
|
+
const err = new Error(reason);
|
|
28
|
+
for (const w of batch.waiters) w.reject(err);
|
|
29
|
+
}, []);
|
|
13
30
|
const abortStream = useCallback(() => {
|
|
14
31
|
if (retryTimerRef.current) {
|
|
15
32
|
clearTimeout(retryTimerRef.current);
|
|
@@ -22,6 +39,10 @@ function usePtySession({ apiUrl, token, onData }) {
|
|
|
22
39
|
}, []);
|
|
23
40
|
const cleanup = useCallback(() => {
|
|
24
41
|
abortStream();
|
|
42
|
+
if (inputAbortRef.current) {
|
|
43
|
+
inputAbortRef.current.abort();
|
|
44
|
+
inputAbortRef.current = null;
|
|
45
|
+
}
|
|
25
46
|
if (sessionIdRef.current) {
|
|
26
47
|
const sid = sessionIdRef.current;
|
|
27
48
|
sessionIdRef.current = null;
|
|
@@ -32,8 +53,9 @@ function usePtySession({ apiUrl, token, onData }) {
|
|
|
32
53
|
}).catch(() => {
|
|
33
54
|
});
|
|
34
55
|
}
|
|
56
|
+
rejectPendingInput("Terminal session is not connected");
|
|
35
57
|
setIsConnected(false);
|
|
36
|
-
}, [apiUrl, token, abortStream]);
|
|
58
|
+
}, [apiUrl, token, abortStream, rejectPendingInput]);
|
|
37
59
|
const connectStream = useCallback(async (sessionId) => {
|
|
38
60
|
abortStream();
|
|
39
61
|
setError(null);
|
|
@@ -139,6 +161,8 @@ function usePtySession({ apiUrl, token, onData }) {
|
|
|
139
161
|
if (!sessionId) throw new Error("No sessionId in response");
|
|
140
162
|
if (!mountedRef.current) return;
|
|
141
163
|
sessionIdRef.current = sessionId;
|
|
164
|
+
inputAbortRef.current = new AbortController();
|
|
165
|
+
ensureDrainRunningRef.current?.();
|
|
142
166
|
await connectStream(sessionId);
|
|
143
167
|
} catch (err) {
|
|
144
168
|
if (err.name === "AbortError") return;
|
|
@@ -169,28 +193,61 @@ function usePtySession({ apiUrl, token, onData }) {
|
|
|
169
193
|
console.error("Failed to resize terminal", err);
|
|
170
194
|
}
|
|
171
195
|
}, [apiUrl, token]);
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
const drainInputQueue = useCallback(async () => {
|
|
197
|
+
while (pendingBatchRef.current.data.length > 0) {
|
|
198
|
+
const sid = sessionIdRef.current;
|
|
199
|
+
if (!sid) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const batch = pendingBatchRef.current;
|
|
203
|
+
pendingBatchRef.current = createEmptyBatch();
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch(`${apiUrl}/terminals/${sid}/input`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: {
|
|
208
|
+
Authorization: `Bearer ${token}`,
|
|
209
|
+
"Content-Type": "application/json"
|
|
210
|
+
},
|
|
211
|
+
credentials: "include",
|
|
212
|
+
body: JSON.stringify({ data: batch.data }),
|
|
213
|
+
signal: inputAbortRef.current?.signal
|
|
214
|
+
});
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
const text = await res.text().catch(() => "");
|
|
217
|
+
throw new Error(text || `Input failed: ${res.status}`);
|
|
218
|
+
}
|
|
219
|
+
for (const w of batch.waiters) w.resolve();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
const isAbort = err?.name === "AbortError";
|
|
222
|
+
const rejection = isAbort ? new Error("Terminal session is not connected") : err;
|
|
223
|
+
if (!isAbort) console.error("Failed to send command", err);
|
|
224
|
+
for (const w of batch.waiters) w.reject(rejection);
|
|
188
225
|
}
|
|
189
|
-
} catch (err) {
|
|
190
|
-
console.error("Failed to send command", err);
|
|
191
|
-
throw err;
|
|
192
226
|
}
|
|
193
227
|
}, [apiUrl, token]);
|
|
228
|
+
const ensureDrainRunning = useCallback(() => {
|
|
229
|
+
if (drainPromiseRef.current) return;
|
|
230
|
+
const run = () => drainInputQueue().finally(() => {
|
|
231
|
+
if (pendingBatchRef.current.data.length > 0 && sessionIdRef.current) {
|
|
232
|
+
drainPromiseRef.current = run();
|
|
233
|
+
} else {
|
|
234
|
+
drainPromiseRef.current = null;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
drainPromiseRef.current = run();
|
|
238
|
+
}, [drainInputQueue]);
|
|
239
|
+
ensureDrainRunningRef.current = ensureDrainRunning;
|
|
240
|
+
const sendCommand = useCallback((command) => {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
if (command.length === 0) {
|
|
243
|
+
resolve();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
pendingBatchRef.current.data += command;
|
|
247
|
+
pendingBatchRef.current.waiters.push({ resolve, reject });
|
|
248
|
+
ensureDrainRunning();
|
|
249
|
+
});
|
|
250
|
+
}, [ensureDrainRunning]);
|
|
194
251
|
useEffect(() => {
|
|
195
252
|
mountedRef.current = true;
|
|
196
253
|
connect();
|
package/dist/globals.css
CHANGED
|
@@ -661,6 +661,9 @@
|
|
|
661
661
|
.collapse {
|
|
662
662
|
visibility: collapse;
|
|
663
663
|
}
|
|
664
|
+
.invisible {
|
|
665
|
+
visibility: hidden;
|
|
666
|
+
}
|
|
664
667
|
.visible {
|
|
665
668
|
visibility: visible;
|
|
666
669
|
}
|
|
@@ -1464,6 +1467,9 @@
|
|
|
1464
1467
|
.shrink-0 {
|
|
1465
1468
|
flex-shrink: 0;
|
|
1466
1469
|
}
|
|
1470
|
+
.grow {
|
|
1471
|
+
flex-grow: 1;
|
|
1472
|
+
}
|
|
1467
1473
|
.caption-bottom {
|
|
1468
1474
|
caption-side: bottom;
|
|
1469
1475
|
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AuthUser, U as UseAuthOptions,
|
|
1
|
+
export { A as AuthUser, S as SandboxMetrics, a as SidecarMetricsPayload, U as UseAuthOptions, b as UseAuthResult, c as UsePtySessionOptions, d as UsePtySessionReturn, e as UseSandboxMetricsOptions, f as UseSandboxMetricsResult, g as createAuthFetcher, u as useApiKey, h as useAuth, i as useLiveTime, j as usePtySession, k as useSandboxMetrics } from './use-sandbox-metrics-B64diPqN.js';
|
|
2
2
|
export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SessionInfo, SidecarAuth, TaskStreamEvent, TerminalStreamEvent, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseSessionStreamOptions, UseSessionStreamResult, UseSidecarAuthOptions, UseToolCallStreamReturn, useAutoScroll, useDropdownMenu, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionStream, useSidecarAuth, useToolCallStream } from './sdk-hooks.js';
|
|
3
3
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
4
4
|
import { S as Session } from './sidecar-CFU2W9j1.js';
|
package/dist/hooks.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAuthFetcher,
|
|
3
3
|
useApiKey,
|
|
4
|
-
useAuth
|
|
5
|
-
|
|
4
|
+
useAuth,
|
|
5
|
+
useLiveTime,
|
|
6
|
+
useSandboxMetrics
|
|
7
|
+
} from "./chunk-DPGIXDAI.js";
|
|
6
8
|
import {
|
|
7
9
|
usePtySession
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ESVYBDGA.js";
|
|
9
11
|
import {
|
|
10
12
|
RealtimeSessionRegistry,
|
|
11
13
|
useDropdownMenu,
|
|
@@ -103,12 +105,14 @@ export {
|
|
|
103
105
|
useCreateSession,
|
|
104
106
|
useDeleteSession,
|
|
105
107
|
useDropdownMenu,
|
|
108
|
+
useLiveTime,
|
|
106
109
|
usePtySession,
|
|
107
110
|
useRealtimeSession,
|
|
108
111
|
useRenameSession,
|
|
109
112
|
useRunCollapseState,
|
|
110
113
|
useRunGroups,
|
|
111
114
|
useSSEStream,
|
|
115
|
+
useSandboxMetrics,
|
|
112
116
|
useSdkSession,
|
|
113
117
|
useSessionStream,
|
|
114
118
|
useSessions,
|
package/dist/index.d.ts
CHANGED
|
@@ -20,13 +20,13 @@ export { c as BillingDashboard, d as BillingDashboardProps, e as PricingCards, f
|
|
|
20
20
|
export { AuthHeader, GitHubLoginButton, LoginLayout, LoginLayoutProps, UserMenu } from './auth.js';
|
|
21
21
|
export { CodeBlock, CodeBlock as CodeBlockDisplay, CopyButton, Markdown, MarkdownProps } from './markdown.js';
|
|
22
22
|
export { AppendUserMessageOptions, ApplySdkEventOptions, BeginAssistantMessageOptions, CompleteAssistantMessageOptions, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, useAutoScroll, useDropdownMenu, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionStream, useSidecarAuth, useToolCallStream } from './sdk-hooks.js';
|
|
23
|
-
export {
|
|
23
|
+
export { S as SandboxMetrics, a as SidecarMetricsPayload, e as UseSandboxMetricsOptions, f as UseSandboxMetricsResult, g as createAuthFetcher, u as useApiKey, h as useAuth, i as useLiveTime, j as usePtySession, k as useSandboxMetrics } from './use-sandbox-metrics-B64diPqN.js';
|
|
24
24
|
export { G as GroupedMessage, R as Run, b as RunStats, T as ToolCategory } from './run-CtFZ6s-D.js';
|
|
25
25
|
export { S as Session } from './sidecar-CFU2W9j1.js';
|
|
26
26
|
export { C as CustomToolRenderer, D as DisplayVariant, T as ToolDisplayMetadata } from './tool-display-Ct9nFAzJ.js';
|
|
27
27
|
export { A as AgentBranding } from './branding-DCi5VEik.js';
|
|
28
28
|
export { A as ActiveProjectActivity, a as ActiveSessionActivityOptions, b as ActiveSessionConnectionOptions, c as ActiveSessionConnectionState, d as ActiveSessionReconnectState, e as ActiveSessionRecord, f as ActiveSessionStatus, g as ActiveSessionTransportMode, h as ActiveSessionsState, R as RegisterActiveSessionOptions, S as SessionProjectKey } from './active-sessions-store-CeOmXgv5.js';
|
|
29
|
-
export { TOOL_CATEGORY_ICONS, cn, copyText, formatDuration, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText } from './utils.js';
|
|
29
|
+
export { TOOL_CATEGORY_ICONS, cn, copyText, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText } from './utils.js';
|
|
30
30
|
import 'class-variance-authority/types';
|
|
31
31
|
import '@radix-ui/react-dialog';
|
|
32
32
|
import 'class-variance-authority';
|
package/dist/index.js
CHANGED
|
@@ -25,11 +25,13 @@ import {
|
|
|
25
25
|
import {
|
|
26
26
|
createAuthFetcher,
|
|
27
27
|
useApiKey,
|
|
28
|
-
useAuth
|
|
29
|
-
|
|
28
|
+
useAuth,
|
|
29
|
+
useLiveTime,
|
|
30
|
+
useSandboxMetrics
|
|
31
|
+
} from "./chunk-DPGIXDAI.js";
|
|
30
32
|
import {
|
|
31
33
|
usePtySession
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-ESVYBDGA.js";
|
|
33
35
|
import {
|
|
34
36
|
RealtimeSessionRegistry,
|
|
35
37
|
useDropdownMenu,
|
|
@@ -104,7 +106,7 @@ import {
|
|
|
104
106
|
TaskBoard,
|
|
105
107
|
TerminalPanel,
|
|
106
108
|
WorkspaceLayout
|
|
107
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-5OQ27N57.js";
|
|
108
110
|
import "./chunk-OEX7NZE3.js";
|
|
109
111
|
import {
|
|
110
112
|
EmptyState,
|
|
@@ -118,7 +120,7 @@ import {
|
|
|
118
120
|
MessageList,
|
|
119
121
|
ThinkingIndicator,
|
|
120
122
|
UserMessage
|
|
121
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-XLG757B6.js";
|
|
122
124
|
import {
|
|
123
125
|
useAutoScroll,
|
|
124
126
|
useRunCollapseState,
|
|
@@ -136,11 +138,13 @@ import {
|
|
|
136
138
|
RunGroup,
|
|
137
139
|
WebSearchPreview,
|
|
138
140
|
WriteFilePreview
|
|
139
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-MQXABZTB.js";
|
|
140
142
|
import {
|
|
143
|
+
formatBytes,
|
|
141
144
|
formatDuration,
|
|
145
|
+
formatUptime,
|
|
142
146
|
truncateText
|
|
143
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-4CLN43XT.js";
|
|
144
148
|
import {
|
|
145
149
|
ToolCallGroup,
|
|
146
150
|
ToolCallStep
|
|
@@ -530,7 +534,9 @@ export {
|
|
|
530
534
|
copyText,
|
|
531
535
|
createAuthFetcher,
|
|
532
536
|
filterFileTree,
|
|
537
|
+
formatBytes,
|
|
533
538
|
formatDuration,
|
|
539
|
+
formatUptime,
|
|
534
540
|
getToolCategory,
|
|
535
541
|
getToolDisplayMetadata,
|
|
536
542
|
getToolErrorText,
|
|
@@ -547,11 +553,13 @@ export {
|
|
|
547
553
|
useDropdownMenu,
|
|
548
554
|
useEditorConnection,
|
|
549
555
|
useEditorContext,
|
|
556
|
+
useLiveTime,
|
|
550
557
|
usePtySession,
|
|
551
558
|
useRealtimeSession,
|
|
552
559
|
useRunCollapseState,
|
|
553
560
|
useRunGroups,
|
|
554
561
|
useSSEStream,
|
|
562
|
+
useSandboxMetrics,
|
|
555
563
|
useSdkSession,
|
|
556
564
|
useSessionStream,
|
|
557
565
|
useSidebar,
|
package/dist/pages.js
CHANGED
|
@@ -502,21 +502,32 @@ function ProvisioningWizard({
|
|
|
502
502
|
const ramStep = alignSliderStep(RAM_MIN, ramMax, RAM_STEP);
|
|
503
503
|
const storageStep = alignSliderStep(STORAGE_MIN, storageMax, STORAGE_STEP);
|
|
504
504
|
const dc = defaultConfig;
|
|
505
|
-
const [envList, setEnvList] = React2.useState(
|
|
506
|
-
environmentsProp
|
|
505
|
+
const [envList, setEnvList] = React2.useState(() => {
|
|
506
|
+
if (environmentsProp) return environmentsProp;
|
|
507
|
+
if (onLoadEnvironments) return [];
|
|
508
|
+
return defaultEnvironments;
|
|
509
|
+
});
|
|
510
|
+
const [isLoadingEnvironments, setIsLoadingEnvironments] = React2.useState(
|
|
511
|
+
() => !environmentsProp && !!onLoadEnvironments
|
|
507
512
|
);
|
|
508
513
|
const onLoadEnvironmentsRef = React2.useRef(onLoadEnvironments);
|
|
509
514
|
onLoadEnvironmentsRef.current = onLoadEnvironments;
|
|
510
515
|
React2.useEffect(() => {
|
|
511
516
|
let cancelled = false;
|
|
512
517
|
if (onLoadEnvironmentsRef.current) {
|
|
518
|
+
setIsLoadingEnvironments(true);
|
|
513
519
|
onLoadEnvironmentsRef.current().then((entries) => {
|
|
514
|
-
if (!cancelled)
|
|
520
|
+
if (!cancelled) {
|
|
521
|
+
setEnvList(entries.map(resolveEnvironment));
|
|
522
|
+
setIsLoadingEnvironments(false);
|
|
523
|
+
}
|
|
515
524
|
}).catch((err) => {
|
|
516
|
-
if (!cancelled)
|
|
525
|
+
if (!cancelled) {
|
|
517
526
|
setLoadError(
|
|
518
527
|
err instanceof Error ? err.message : "Failed to load environments"
|
|
519
528
|
);
|
|
529
|
+
setIsLoadingEnvironments(false);
|
|
530
|
+
}
|
|
520
531
|
});
|
|
521
532
|
} else if (environmentsProp) {
|
|
522
533
|
setEnvList(environmentsProp);
|
|
@@ -531,9 +542,15 @@ function ProvisioningWizard({
|
|
|
531
542
|
effectiveDefault ?? environments[0]?.id ?? ""
|
|
532
543
|
);
|
|
533
544
|
React2.useEffect(() => {
|
|
545
|
+
if (envList.length === 0) return;
|
|
534
546
|
if (effectiveDefault && envList.some((e) => e.id === effectiveDefault)) {
|
|
535
547
|
setSelectedEnv(effectiveDefault);
|
|
548
|
+
return;
|
|
536
549
|
}
|
|
550
|
+
setSelectedEnv((prev) => {
|
|
551
|
+
if (prev && envList.some((e) => e.id === prev)) return prev;
|
|
552
|
+
return envList[0]?.id ?? "";
|
|
553
|
+
});
|
|
537
554
|
}, [envList, effectiveDefault]);
|
|
538
555
|
const [cpuCores, setCpuCores] = React2.useState(
|
|
539
556
|
snapSliderValue(dc?.cpuCores ?? 4, CPU_MIN, cpuMax, cpuStep)
|
|
@@ -797,7 +814,23 @@ function ProvisioningWizard({
|
|
|
797
814
|
/* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 text-primary", children: /* @__PURE__ */ jsx2(Layers, { className: "h-5 w-5" }) }),
|
|
798
815
|
/* @__PURE__ */ jsx2("h2", { className: "text-lg font-bold text-foreground tracking-tight", children: "Environment Selection" })
|
|
799
816
|
] }),
|
|
800
|
-
/* @__PURE__ */ jsx2("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-3", children: environments.map((
|
|
817
|
+
/* @__PURE__ */ jsx2("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-3", children: isLoadingEnvironments && environments.length === 0 ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs2(
|
|
818
|
+
"div",
|
|
819
|
+
{
|
|
820
|
+
className: "p-4 rounded-[16px] border border-border bg-card/50 animate-pulse",
|
|
821
|
+
"aria-hidden": "true",
|
|
822
|
+
children: [
|
|
823
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-start mb-3", children: [
|
|
824
|
+
/* @__PURE__ */ jsx2("div", { className: "w-10 h-10 rounded-full bg-muted/60 border border-border" }),
|
|
825
|
+
/* @__PURE__ */ jsx2("div", { className: "w-5 h-5 rounded-full border-2 border-border" })
|
|
826
|
+
] }),
|
|
827
|
+
/* @__PURE__ */ jsx2("div", { className: "h-3 w-1/3 rounded bg-muted/60 mb-2" }),
|
|
828
|
+
/* @__PURE__ */ jsx2("div", { className: "h-2.5 w-5/6 rounded bg-muted/50 mb-1.5" }),
|
|
829
|
+
/* @__PURE__ */ jsx2("div", { className: "h-2.5 w-2/3 rounded bg-muted/50" })
|
|
830
|
+
]
|
|
831
|
+
},
|
|
832
|
+
`env-skeleton-${i}`
|
|
833
|
+
)) : environments.map((env) => /* @__PURE__ */ jsxs2(
|
|
801
834
|
"button",
|
|
802
835
|
{
|
|
803
836
|
type: "button",
|
package/dist/run.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -661,6 +661,9 @@
|
|
|
661
661
|
.collapse {
|
|
662
662
|
visibility: collapse;
|
|
663
663
|
}
|
|
664
|
+
.invisible {
|
|
665
|
+
visibility: hidden;
|
|
666
|
+
}
|
|
664
667
|
.visible {
|
|
665
668
|
visibility: visible;
|
|
666
669
|
}
|
|
@@ -1464,6 +1467,9 @@
|
|
|
1464
1467
|
.shrink-0 {
|
|
1465
1468
|
flex-shrink: 0;
|
|
1466
1469
|
}
|
|
1470
|
+
.grow {
|
|
1471
|
+
flex-grow: 1;
|
|
1472
|
+
}
|
|
1467
1473
|
.caption-bottom {
|
|
1468
1474
|
caption-side: bottom;
|
|
1469
1475
|
}
|
package/dist/terminal.js
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
interface AuthUser {
|
|
2
|
+
customer_id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
tier: string;
|
|
6
|
+
github?: {
|
|
7
|
+
login: string;
|
|
8
|
+
connected: boolean;
|
|
9
|
+
} | null;
|
|
10
|
+
session_expires_at?: string;
|
|
11
|
+
}
|
|
12
|
+
interface UseAuthOptions {
|
|
13
|
+
apiBaseUrl: string;
|
|
14
|
+
revalidateOnFocus?: boolean;
|
|
15
|
+
shouldRetryOnError?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface UseAuthResult {
|
|
18
|
+
user: AuthUser | null;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
isError: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
mutate: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Hook for managing authentication state.
|
|
26
|
+
* Fetches user session from the API and provides loading/error states.
|
|
27
|
+
*/
|
|
28
|
+
declare function useAuth({ apiBaseUrl, revalidateOnFocus, shouldRetryOnError, }: UseAuthOptions): UseAuthResult;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a fetcher function that includes auth credentials.
|
|
31
|
+
* Uses both cookie-based session and localStorage API key for backwards compatibility.
|
|
32
|
+
*/
|
|
33
|
+
declare function createAuthFetcher(_apiBaseUrl: string): <T = unknown>(url: string, options?: RequestInit) => Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Hook to get the API key from localStorage.
|
|
36
|
+
* For backwards compatibility with API key-based auth.
|
|
37
|
+
*/
|
|
38
|
+
declare function useApiKey(): string | null;
|
|
39
|
+
|
|
40
|
+
interface UsePtySessionOptions {
|
|
41
|
+
/** Base URL of the sidecar (e.g. "http://localhost:9100"). */
|
|
42
|
+
apiUrl: string;
|
|
43
|
+
/** Bearer token for authentication. */
|
|
44
|
+
token: string;
|
|
45
|
+
/** Called with raw PTY output (may contain ANSI escape codes). */
|
|
46
|
+
onData: (data: string) => void;
|
|
47
|
+
}
|
|
48
|
+
interface UsePtySessionReturn {
|
|
49
|
+
/** Whether the SSE stream is connected and receiving data. */
|
|
50
|
+
isConnected: boolean;
|
|
51
|
+
/** Connection or API error, if any. */
|
|
52
|
+
error: string | null;
|
|
53
|
+
/** Send a command to the PTY session. */
|
|
54
|
+
sendCommand: (command: string) => Promise<void>;
|
|
55
|
+
/** Safely resize the remote PTY. */
|
|
56
|
+
resizeTerminal: (cols: number, rows: number) => Promise<void>;
|
|
57
|
+
/** Tear down and reconnect. */
|
|
58
|
+
reconnect: () => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Manages a PTY session against the sidecar terminal API.
|
|
62
|
+
*
|
|
63
|
+
* Protocol:
|
|
64
|
+
* - POST /terminals → create session → { data: { sessionId } }
|
|
65
|
+
* - GET /terminals/{id}/stream → SSE output (raw PTY with ANSI codes)
|
|
66
|
+
* - POST /terminals/{id}/input → send input { data: "..." }
|
|
67
|
+
* - DELETE /terminals/{id} → close session
|
|
68
|
+
*/
|
|
69
|
+
declare function usePtySession({ apiUrl, token, onData }: UsePtySessionOptions): UsePtySessionReturn;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns `Date.now()` and re-renders every `intervalMs` so derived
|
|
73
|
+
* values like "uptime" tick forward without polling upstream data.
|
|
74
|
+
*/
|
|
75
|
+
declare function useLiveTime(intervalMs?: number): number;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Shape returned by the sidecar `/metrics/json` endpoint. Only the
|
|
79
|
+
* fields read by this hook are modeled; the sidecar may add more.
|
|
80
|
+
*/
|
|
81
|
+
interface SidecarMetricsPayload {
|
|
82
|
+
process?: {
|
|
83
|
+
memoryBytes?: {
|
|
84
|
+
rss?: number;
|
|
85
|
+
heapTotal?: number;
|
|
86
|
+
heapUsed?: number;
|
|
87
|
+
external?: number;
|
|
88
|
+
arrayBuffers?: number;
|
|
89
|
+
};
|
|
90
|
+
cpuSeconds?: {
|
|
91
|
+
user?: number;
|
|
92
|
+
system?: number;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
interface SandboxMetrics {
|
|
97
|
+
/**
|
|
98
|
+
* CPU% derived from consecutive samples. `null` on the first sample
|
|
99
|
+
* because a delta is required. Can exceed 100 on multi-core hosts.
|
|
100
|
+
*/
|
|
101
|
+
cpuPercent: number | null;
|
|
102
|
+
rssBytes: number;
|
|
103
|
+
heapUsedBytes: number;
|
|
104
|
+
heapTotalBytes: number;
|
|
105
|
+
}
|
|
106
|
+
interface UseSandboxMetricsOptions {
|
|
107
|
+
/** Sandbox API base URL, e.g. `https://api.tangle.tools`. */
|
|
108
|
+
apiBaseUrl: string;
|
|
109
|
+
/** Sandbox id; when falsy the hook stays idle. */
|
|
110
|
+
sandboxId?: string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Optional bearer token. When omitted the fetch still sends
|
|
113
|
+
* credentials so a cookie session can authenticate the proxy.
|
|
114
|
+
*/
|
|
115
|
+
token?: string | null;
|
|
116
|
+
/** Pause polling when false. Defaults to true. */
|
|
117
|
+
enabled?: boolean;
|
|
118
|
+
/** Poll cadence; clamped to a 500ms floor. Defaults to 3000. */
|
|
119
|
+
intervalMs?: number;
|
|
120
|
+
}
|
|
121
|
+
interface UseSandboxMetricsResult {
|
|
122
|
+
metrics: SandboxMetrics | null;
|
|
123
|
+
/**
|
|
124
|
+
* True only until the first successful sample has arrived (or the
|
|
125
|
+
* first one after the target `sandboxId` changes). Subsequent polls
|
|
126
|
+
* do not flip this back to true, so consumers can gate a spinner
|
|
127
|
+
* on it without it flashing on every cycle.
|
|
128
|
+
*/
|
|
129
|
+
loading: boolean;
|
|
130
|
+
error: Error | null;
|
|
131
|
+
/** Wall-clock ms of the last successful sample, or null. */
|
|
132
|
+
lastUpdatedAt: number | null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Polls the sandbox's sidecar metrics through the API proxy and
|
|
136
|
+
* derives a CPU% value from consecutive cumulative-CPU samples. Used
|
|
137
|
+
* by the sandbox overview dashboard to drive live CPU/memory panels.
|
|
138
|
+
*/
|
|
139
|
+
declare function useSandboxMetrics({ apiBaseUrl, sandboxId, token, enabled, intervalMs, }: UseSandboxMetricsOptions): UseSandboxMetricsResult;
|
|
140
|
+
|
|
141
|
+
export { type AuthUser as A, type SandboxMetrics as S, type UseAuthOptions as U, type SidecarMetricsPayload as a, type UseAuthResult as b, type UsePtySessionOptions as c, type UsePtySessionReturn as d, type UseSandboxMetricsOptions as e, type UseSandboxMetricsResult as f, createAuthFetcher as g, useAuth as h, useLiveTime as i, usePtySession as j, useSandboxMetrics as k, useApiKey as u };
|
package/dist/utils.d.ts
CHANGED
|
@@ -16,6 +16,22 @@ declare function copyText(text: string): Promise<boolean>;
|
|
|
16
16
|
declare function formatDuration(ms: number): string;
|
|
17
17
|
/** Truncate text to `max` characters, appending "..." if truncated. */
|
|
18
18
|
declare function truncateText(text: string, max: number): string;
|
|
19
|
+
/**
|
|
20
|
+
* Format an uptime duration in milliseconds with progressive
|
|
21
|
+
* granularity, so short-lived sandboxes don't render as "0d 0h".
|
|
22
|
+
* - < 60s → "Ns"
|
|
23
|
+
* - < 60m → "Nm Ss"
|
|
24
|
+
* - < 24h → "Nh Mm"
|
|
25
|
+
* - otherwise → "Nd Hh"
|
|
26
|
+
*/
|
|
27
|
+
declare function formatUptime(ms: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Format a byte count using binary units (KiB/MiB/GiB, surfaced as
|
|
30
|
+
* "KB/MB/GB" for readability). KB and MB use one decimal below 10 and
|
|
31
|
+
* round above; GB keeps two decimals below 10 so half-GB changes stay
|
|
32
|
+
* visible on memory dashboards, and drops to one decimal above.
|
|
33
|
+
*/
|
|
34
|
+
declare function formatBytes(bytes: number): string;
|
|
19
35
|
|
|
20
36
|
declare function timeAgo(ts: number): string;
|
|
21
37
|
|
|
@@ -25,4 +41,4 @@ declare function getToolDisplayMetadata(part: ToolPart): ToolDisplayMetadata;
|
|
|
25
41
|
/** Extract error text from a tool part, if any. */
|
|
26
42
|
declare function getToolErrorText(part: ToolPart, fallback?: string): string | undefined;
|
|
27
43
|
|
|
28
|
-
export { TOOL_CATEGORY_ICONS, cn, copyText, formatDuration, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText };
|
|
44
|
+
export { TOOL_CATEGORY_ICONS, cn, copyText, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText };
|
package/dist/utils.js
CHANGED
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
timeAgo
|
|
4
4
|
} from "./chunk-QD4QE5P5.js";
|
|
5
5
|
import {
|
|
6
|
+
formatBytes,
|
|
6
7
|
formatDuration,
|
|
8
|
+
formatUptime,
|
|
7
9
|
truncateText
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4CLN43XT.js";
|
|
9
11
|
import {
|
|
10
12
|
TOOL_CATEGORY_ICONS,
|
|
11
13
|
getToolCategory,
|
|
@@ -19,7 +21,9 @@ export {
|
|
|
19
21
|
TOOL_CATEGORY_ICONS,
|
|
20
22
|
cn,
|
|
21
23
|
copyText,
|
|
24
|
+
formatBytes,
|
|
22
25
|
formatDuration,
|
|
26
|
+
formatUptime,
|
|
23
27
|
getToolCategory,
|
|
24
28
|
getToolDisplayMetadata,
|
|
25
29
|
getToolErrorText,
|
package/dist/workspace.js
CHANGED
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
TaskBoard,
|
|
14
14
|
TerminalPanel,
|
|
15
15
|
WorkspaceLayout
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-5OQ27N57.js";
|
|
17
17
|
import "./chunk-OEX7NZE3.js";
|
|
18
18
|
import "./chunk-MA7YKRUP.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-XLG757B6.js";
|
|
20
20
|
import "./chunk-54SQQMMM.js";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-MQXABZTB.js";
|
|
22
|
+
import "./chunk-4CLN43XT.js";
|
|
23
23
|
import "./chunk-MT5FJ3ZT.js";
|
|
24
24
|
import "./chunk-BX6AQMUS.js";
|
|
25
25
|
import "./chunk-ZNCEM5CD.js";
|
package/package.json
CHANGED
package/dist/chunk-HRMUF35V.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// src/utils/format.ts
|
|
2
|
-
function formatDuration(ms) {
|
|
3
|
-
if (ms < 1e3) return "<1s";
|
|
4
|
-
const seconds = Math.floor(ms / 1e3);
|
|
5
|
-
if (seconds < 60) return `${seconds}s`;
|
|
6
|
-
const minutes = Math.floor(seconds / 60);
|
|
7
|
-
const remaining = seconds % 60;
|
|
8
|
-
return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`;
|
|
9
|
-
}
|
|
10
|
-
function truncateText(text, max) {
|
|
11
|
-
const cleaned = text.replace(/\s+/g, " ").trim();
|
|
12
|
-
if (cleaned.length <= max) return cleaned;
|
|
13
|
-
return cleaned.slice(0, max).trim() + "...";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
formatDuration,
|
|
18
|
-
truncateText
|
|
19
|
-
};
|
package/dist/chunk-S7OXQTST.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// src/hooks/use-auth.ts
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
function useAuth({
|
|
4
|
-
apiBaseUrl,
|
|
5
|
-
revalidateOnFocus = false,
|
|
6
|
-
shouldRetryOnError = false
|
|
7
|
-
}) {
|
|
8
|
-
const [user, setUser] = React.useState(null);
|
|
9
|
-
const [isLoading, setIsLoading] = React.useState(true);
|
|
10
|
-
const [error, setError] = React.useState(null);
|
|
11
|
-
const retryTimerRef = React.useRef(null);
|
|
12
|
-
const abortRef = React.useRef(null);
|
|
13
|
-
const fetchSession = React.useCallback(async () => {
|
|
14
|
-
abortRef.current?.abort();
|
|
15
|
-
const controller = new AbortController();
|
|
16
|
-
abortRef.current = controller;
|
|
17
|
-
setIsLoading(true);
|
|
18
|
-
setError(null);
|
|
19
|
-
try {
|
|
20
|
-
const res = await fetch(`${apiBaseUrl}/auth/session`, {
|
|
21
|
-
credentials: "include",
|
|
22
|
-
signal: controller.signal
|
|
23
|
-
});
|
|
24
|
-
if (!res.ok) {
|
|
25
|
-
throw new Error("Not authenticated");
|
|
26
|
-
}
|
|
27
|
-
const data = await res.json();
|
|
28
|
-
if (data.success && data.data) {
|
|
29
|
-
setUser(data.data);
|
|
30
|
-
} else {
|
|
31
|
-
setUser(null);
|
|
32
|
-
}
|
|
33
|
-
} catch (err) {
|
|
34
|
-
if (err.name === "AbortError") return;
|
|
35
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
36
|
-
setUser(null);
|
|
37
|
-
if (shouldRetryOnError) {
|
|
38
|
-
if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
|
|
39
|
-
retryTimerRef.current = setTimeout(fetchSession, 5e3);
|
|
40
|
-
}
|
|
41
|
-
} finally {
|
|
42
|
-
if (!controller.signal.aborted) {
|
|
43
|
-
setIsLoading(false);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}, [apiBaseUrl, shouldRetryOnError]);
|
|
47
|
-
React.useEffect(() => {
|
|
48
|
-
fetchSession();
|
|
49
|
-
return () => {
|
|
50
|
-
abortRef.current?.abort();
|
|
51
|
-
if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
|
|
52
|
-
};
|
|
53
|
-
}, [fetchSession]);
|
|
54
|
-
React.useEffect(() => {
|
|
55
|
-
if (!revalidateOnFocus) return;
|
|
56
|
-
const handleFocus = () => {
|
|
57
|
-
fetchSession();
|
|
58
|
-
};
|
|
59
|
-
window.addEventListener("focus", handleFocus);
|
|
60
|
-
return () => window.removeEventListener("focus", handleFocus);
|
|
61
|
-
}, [revalidateOnFocus, fetchSession]);
|
|
62
|
-
return {
|
|
63
|
-
user,
|
|
64
|
-
isLoading,
|
|
65
|
-
isError: !!error,
|
|
66
|
-
error,
|
|
67
|
-
mutate: fetchSession
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function createAuthFetcher(_apiBaseUrl) {
|
|
71
|
-
return async function authFetcher(url, options) {
|
|
72
|
-
const res = await fetch(url, {
|
|
73
|
-
...options,
|
|
74
|
-
credentials: "include",
|
|
75
|
-
headers: {
|
|
76
|
-
...options?.headers
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
if (!res.ok) {
|
|
80
|
-
throw new Error(`Request failed with status ${res.status}`);
|
|
81
|
-
}
|
|
82
|
-
return res.json();
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function useApiKey() {
|
|
86
|
-
const [apiKey, setApiKey] = React.useState(null);
|
|
87
|
-
React.useEffect(() => {
|
|
88
|
-
if (typeof window !== "undefined") {
|
|
89
|
-
setApiKey(localStorage.getItem("apiKey"));
|
|
90
|
-
}
|
|
91
|
-
}, []);
|
|
92
|
-
return apiKey;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export {
|
|
96
|
-
useAuth,
|
|
97
|
-
createAuthFetcher,
|
|
98
|
-
useApiKey
|
|
99
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
interface AuthUser {
|
|
2
|
-
customer_id: string;
|
|
3
|
-
email: string;
|
|
4
|
-
name?: string;
|
|
5
|
-
tier: string;
|
|
6
|
-
github?: {
|
|
7
|
-
login: string;
|
|
8
|
-
connected: boolean;
|
|
9
|
-
} | null;
|
|
10
|
-
session_expires_at?: string;
|
|
11
|
-
}
|
|
12
|
-
interface UseAuthOptions {
|
|
13
|
-
apiBaseUrl: string;
|
|
14
|
-
revalidateOnFocus?: boolean;
|
|
15
|
-
shouldRetryOnError?: boolean;
|
|
16
|
-
}
|
|
17
|
-
interface UseAuthResult {
|
|
18
|
-
user: AuthUser | null;
|
|
19
|
-
isLoading: boolean;
|
|
20
|
-
isError: boolean;
|
|
21
|
-
error: Error | null;
|
|
22
|
-
mutate: () => Promise<void>;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Hook for managing authentication state.
|
|
26
|
-
* Fetches user session from the API and provides loading/error states.
|
|
27
|
-
*/
|
|
28
|
-
declare function useAuth({ apiBaseUrl, revalidateOnFocus, shouldRetryOnError, }: UseAuthOptions): UseAuthResult;
|
|
29
|
-
/**
|
|
30
|
-
* Creates a fetcher function that includes auth credentials.
|
|
31
|
-
* Uses both cookie-based session and localStorage API key for backwards compatibility.
|
|
32
|
-
*/
|
|
33
|
-
declare function createAuthFetcher(_apiBaseUrl: string): <T = unknown>(url: string, options?: RequestInit) => Promise<T>;
|
|
34
|
-
/**
|
|
35
|
-
* Hook to get the API key from localStorage.
|
|
36
|
-
* For backwards compatibility with API key-based auth.
|
|
37
|
-
*/
|
|
38
|
-
declare function useApiKey(): string | null;
|
|
39
|
-
|
|
40
|
-
interface UsePtySessionOptions {
|
|
41
|
-
/** Base URL of the sidecar (e.g. "http://localhost:9100"). */
|
|
42
|
-
apiUrl: string;
|
|
43
|
-
/** Bearer token for authentication. */
|
|
44
|
-
token: string;
|
|
45
|
-
/** Called with raw PTY output (may contain ANSI escape codes). */
|
|
46
|
-
onData: (data: string) => void;
|
|
47
|
-
}
|
|
48
|
-
interface UsePtySessionReturn {
|
|
49
|
-
/** Whether the SSE stream is connected and receiving data. */
|
|
50
|
-
isConnected: boolean;
|
|
51
|
-
/** Connection or API error, if any. */
|
|
52
|
-
error: string | null;
|
|
53
|
-
/** Send a command to the PTY session. */
|
|
54
|
-
sendCommand: (command: string) => Promise<void>;
|
|
55
|
-
/** Safely resize the remote PTY. */
|
|
56
|
-
resizeTerminal: (cols: number, rows: number) => Promise<void>;
|
|
57
|
-
/** Tear down and reconnect. */
|
|
58
|
-
reconnect: () => void;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Manages a PTY session against the sidecar terminal API.
|
|
62
|
-
*
|
|
63
|
-
* Protocol:
|
|
64
|
-
* - POST /terminals → create session → { data: { sessionId } }
|
|
65
|
-
* - GET /terminals/{id}/stream → SSE output (raw PTY with ANSI codes)
|
|
66
|
-
* - POST /terminals/{id}/input → send input { data: "..." }
|
|
67
|
-
* - DELETE /terminals/{id} → close session
|
|
68
|
-
*/
|
|
69
|
-
declare function usePtySession({ apiUrl, token, onData }: UsePtySessionOptions): UsePtySessionReturn;
|
|
70
|
-
|
|
71
|
-
export { type AuthUser as A, type UseAuthOptions as U, type UseAuthResult as a, type UsePtySessionOptions as b, type UsePtySessionReturn as c, createAuthFetcher as d, useAuth as e, usePtySession as f, useApiKey as u };
|