mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 16:01:05 +00:00
Improve Daytona sandbox provisioning and frontend UI
Refactor git clone script in Daytona provider to use cleaner shell logic for GitHub token authentication and branch checkout. Add support for private repository clones with token-based auth. Improve Daytona provider error handling and git configuration setup. Frontend improvements include enhanced dev panel, workspace dashboard, sidebar navigation, and UI components for better task/session management. Update interest manager and backend client to support improved session state handling. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8fb19b50da
commit
098b8113f3
19 changed files with 394 additions and 130 deletions
|
|
@ -54,7 +54,7 @@ export interface SandboxSessionRecord {
|
|||
lastConnectionId: string;
|
||||
createdAt: number;
|
||||
destroyedAt?: number;
|
||||
status?: "running" | "idle" | "error";
|
||||
status?: "pending_provision" | "pending_session_create" | "ready" | "running" | "idle" | "error";
|
||||
}
|
||||
|
||||
export interface SandboxSessionEventRecord {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import type { TopicData, TopicKey, TopicParams } from "./topics.js";
|
|||
|
||||
export type TopicStatus = "loading" | "connected" | "error";
|
||||
|
||||
export interface DebugInterestTopic {
|
||||
topicKey: TopicKey;
|
||||
cacheKey: string;
|
||||
listenerCount: number;
|
||||
status: TopicStatus;
|
||||
lastRefreshAt: number | null;
|
||||
}
|
||||
|
||||
export interface TopicState<K extends TopicKey> {
|
||||
data: TopicData<K> | undefined;
|
||||
status: TopicStatus;
|
||||
|
|
@ -20,5 +28,6 @@ export interface InterestManager {
|
|||
getSnapshot<K extends TopicKey>(topicKey: K, params: TopicParams<K>): TopicData<K> | undefined;
|
||||
getStatus<K extends TopicKey>(topicKey: K, params: TopicParams<K>): TopicStatus;
|
||||
getError<K extends TopicKey>(topicKey: K, params: TopicParams<K>): Error | null;
|
||||
listDebugTopics(): DebugInterestTopic[];
|
||||
dispose(): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { BackendClient } from "../backend-client.js";
|
||||
import type { InterestManager, TopicStatus } from "./manager.js";
|
||||
import type { DebugInterestTopic, InterestManager, TopicStatus } from "./manager.js";
|
||||
import { topicDefinitions, type TopicData, type TopicDefinition, type TopicKey, type TopicParams } from "./topics.js";
|
||||
|
||||
const GRACE_PERIOD_MS = 30_000;
|
||||
|
|
@ -19,7 +19,7 @@ export class RemoteInterestManager implements InterestManager {
|
|||
let entry = this.entries.get(cacheKey);
|
||||
|
||||
if (!entry) {
|
||||
entry = new TopicEntry(definition, this.backend, params as any);
|
||||
entry = new TopicEntry(topicKey, cacheKey, definition, this.backend, params as any);
|
||||
this.entries.set(cacheKey, entry);
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +53,13 @@ export class RemoteInterestManager implements InterestManager {
|
|||
return this.entries.get((topicDefinitions[topicKey] as any).key(params))?.error ?? null;
|
||||
}
|
||||
|
||||
listDebugTopics(): DebugInterestTopic[] {
|
||||
return [...this.entries.values()]
|
||||
.filter((entry) => entry.listenerCount > 0)
|
||||
.map((entry) => entry.getDebugTopic())
|
||||
.sort((left, right) => left.cacheKey.localeCompare(right.cacheKey));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const entry of this.entries.values()) {
|
||||
entry.dispose();
|
||||
|
|
@ -66,6 +73,7 @@ class TopicEntry<TData, TParams, TEvent> {
|
|||
status: TopicStatus = "loading";
|
||||
error: Error | null = null;
|
||||
listenerCount = 0;
|
||||
lastRefreshAt: number | null = null;
|
||||
|
||||
private readonly listeners = new Set<() => void>();
|
||||
private conn: Awaited<ReturnType<TopicDefinition<TData, TParams, TEvent>["connect"]>> | null = null;
|
||||
|
|
@ -76,11 +84,23 @@ class TopicEntry<TData, TParams, TEvent> {
|
|||
private started = false;
|
||||
|
||||
constructor(
|
||||
private readonly topicKey: TopicKey,
|
||||
private readonly cacheKey: string,
|
||||
private readonly definition: TopicDefinition<TData, TParams, TEvent>,
|
||||
private readonly backend: BackendClient,
|
||||
private readonly params: TParams,
|
||||
) {}
|
||||
|
||||
getDebugTopic(): DebugInterestTopic {
|
||||
return {
|
||||
topicKey: this.topicKey,
|
||||
cacheKey: this.cacheKey,
|
||||
listenerCount: this.listenerCount,
|
||||
status: this.status,
|
||||
lastRefreshAt: this.lastRefreshAt,
|
||||
};
|
||||
}
|
||||
|
||||
addListener(listener: () => void): void {
|
||||
this.listeners.add(listener);
|
||||
this.listenerCount = this.listeners.size;
|
||||
|
|
@ -125,6 +145,7 @@ class TopicEntry<TData, TParams, TEvent> {
|
|||
this.data = undefined;
|
||||
this.status = "loading";
|
||||
this.error = null;
|
||||
this.lastRefreshAt = null;
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +161,7 @@ class TopicEntry<TData, TParams, TEvent> {
|
|||
return;
|
||||
}
|
||||
this.data = this.definition.applyEvent(this.data, event);
|
||||
this.lastRefreshAt = Date.now();
|
||||
this.notify();
|
||||
});
|
||||
this.unsubscribeError = this.conn.onError((error: unknown) => {
|
||||
|
|
@ -149,6 +171,7 @@ class TopicEntry<TData, TParams, TEvent> {
|
|||
});
|
||||
this.data = await this.definition.fetchInitial(this.backend, this.params);
|
||||
this.status = "connected";
|
||||
this.lastRefreshAt = Date.now();
|
||||
this.started = true;
|
||||
this.notify();
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,14 @@ describe("RemoteInterestManager", () => {
|
|||
expect(backend.getWorkspaceSummary).toHaveBeenCalledTimes(1);
|
||||
expect(manager.getStatus("workspace", params)).toBe("connected");
|
||||
expect(manager.getSnapshot("workspace", params)?.taskSummaries[0]?.title).toBe("Initial task");
|
||||
expect(manager.listDebugTopics()).toEqual([
|
||||
expect.objectContaining({
|
||||
topicKey: "workspace",
|
||||
cacheKey: "workspace:ws-1",
|
||||
listenerCount: 2,
|
||||
status: "connected",
|
||||
}),
|
||||
]);
|
||||
|
||||
conn.emit("workspaceUpdated", {
|
||||
type: "taskSummaryUpdated",
|
||||
|
|
@ -123,6 +131,7 @@ describe("RemoteInterestManager", () => {
|
|||
expect(manager.getSnapshot("workspace", params)?.taskSummaries[0]?.title).toBe("Updated task");
|
||||
expect(listenerA).toHaveBeenCalled();
|
||||
expect(listenerB).toHaveBeenCalled();
|
||||
expect(manager.listDebugTopics()[0]?.lastRefreshAt).toEqual(expect.any(Number));
|
||||
|
||||
unsubscribeA();
|
||||
unsubscribeB();
|
||||
|
|
@ -140,6 +149,7 @@ describe("RemoteInterestManager", () => {
|
|||
unsubscribeA();
|
||||
|
||||
vi.advanceTimersByTime(29_000);
|
||||
expect(manager.listDebugTopics()).toEqual([]);
|
||||
|
||||
const unsubscribeB = manager.subscribe("workspace", params, () => {});
|
||||
await flushAsyncWork();
|
||||
|
|
@ -148,6 +158,7 @@ describe("RemoteInterestManager", () => {
|
|||
expect(conn.disposeCount).toBe(0);
|
||||
|
||||
unsubscribeB();
|
||||
expect(manager.listDebugTopics()).toEqual([]);
|
||||
vi.advanceTimersByTime(30_000);
|
||||
|
||||
expect(conn.disposeCount).toBe(1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue