mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 06:04:56 +00:00
Add star repo onboarding flow (#232)
This commit is contained in:
parent
d2346bafb3
commit
34a0587cbc
9 changed files with 330 additions and 89 deletions
12
factory/factory-cloud.md
Normal file
12
factory/factory-cloud.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Factory Cloud
|
||||||
|
|
||||||
|
## Mock Server
|
||||||
|
|
||||||
|
If you are running the mock server with Beat instead of `docker compose`, use a team accession for the process so it does not terminate when your message is finished.
|
||||||
|
|
||||||
|
A detached `tmux` session is acceptable for this. Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tmux new-session -d -s mock-ui-4180 \
|
||||||
|
'cd /Users/nathan/conductor/workspaces/sandbox-agent/provo && OPENHANDOFF_FRONTEND_CLIENT_MODE=mock pnpm --filter @openhandoff/frontend exec vite --host localhost --port 4180'
|
||||||
|
```
|
||||||
|
|
@ -25,6 +25,8 @@ import type {
|
||||||
RepoStackActionInput,
|
RepoStackActionInput,
|
||||||
RepoStackActionResult,
|
RepoStackActionResult,
|
||||||
RepoRecord,
|
RepoRecord,
|
||||||
|
StarSandboxAgentRepoInput,
|
||||||
|
StarSandboxAgentRepoResult,
|
||||||
SwitchResult,
|
SwitchResult,
|
||||||
WorkspaceUseInput,
|
WorkspaceUseInput,
|
||||||
} from "@openhandoff/shared";
|
} from "@openhandoff/shared";
|
||||||
|
|
@ -59,6 +61,7 @@ interface RepoOverviewInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORKSPACE_QUEUE_NAMES = ["workspace.command.addRepo", "workspace.command.createHandoff", "workspace.command.refreshProviderProfiles"] as const;
|
const WORKSPACE_QUEUE_NAMES = ["workspace.command.addRepo", "workspace.command.createHandoff", "workspace.command.refreshProviderProfiles"] as const;
|
||||||
|
const SANDBOX_AGENT_REPO = "rivet-dev/sandbox-agent";
|
||||||
|
|
||||||
type WorkspaceQueueName = (typeof WORKSPACE_QUEUE_NAMES)[number];
|
type WorkspaceQueueName = (typeof WORKSPACE_QUEUE_NAMES)[number];
|
||||||
|
|
||||||
|
|
@ -415,6 +418,16 @@ export const workspaceActions = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async starSandboxAgentRepo(c: any, input: StarSandboxAgentRepoInput): Promise<StarSandboxAgentRepoResult> {
|
||||||
|
assertWorkspace(c, input.workspaceId);
|
||||||
|
const { driver } = getActorRuntimeContext();
|
||||||
|
await driver.github.starRepository(SANDBOX_AGENT_REPO);
|
||||||
|
return {
|
||||||
|
repo: SANDBOX_AGENT_REPO,
|
||||||
|
starredAt: Date.now(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async getWorkbench(c: any, input: WorkspaceUseInput): Promise<HandoffWorkbenchSnapshot> {
|
async getWorkbench(c: any, input: WorkspaceUseInput): Promise<HandoffWorkbenchSnapshot> {
|
||||||
assertWorkspace(c, input.workspaceId);
|
assertWorkspace(c, input.workspaceId);
|
||||||
return await buildWorkbenchSnapshot(c);
|
return await buildWorkbenchSnapshot(c);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import {
|
||||||
gitSpiceSyncRepo,
|
gitSpiceSyncRepo,
|
||||||
gitSpiceTrackBranch,
|
gitSpiceTrackBranch,
|
||||||
} from "./integrations/git-spice/index.js";
|
} from "./integrations/git-spice/index.js";
|
||||||
import { listPullRequests, createPr } from "./integrations/github/index.js";
|
import { listPullRequests, createPr, starRepository } from "./integrations/github/index.js";
|
||||||
import { SandboxAgentClient } from "./integrations/sandbox-agent/client.js";
|
import { SandboxAgentClient } from "./integrations/sandbox-agent/client.js";
|
||||||
import { DaytonaClient } from "./integrations/daytona/client.js";
|
import { DaytonaClient } from "./integrations/daytona/client.js";
|
||||||
|
|
||||||
|
|
@ -59,6 +59,7 @@ export interface StackDriver {
|
||||||
export interface GithubDriver {
|
export interface GithubDriver {
|
||||||
listPullRequests(repoPath: string): Promise<PullRequestSnapshot[]>;
|
listPullRequests(repoPath: string): Promise<PullRequestSnapshot[]>;
|
||||||
createPr(repoPath: string, headBranch: string, title: string, body?: string): Promise<{ number: number; url: string }>;
|
createPr(repoPath: string, headBranch: string, title: string, body?: string): Promise<{ number: number; url: string }>;
|
||||||
|
starRepository(repoFullName: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SandboxAgentClientLike {
|
export interface SandboxAgentClientLike {
|
||||||
|
|
@ -131,6 +132,7 @@ export function createDefaultDriver(): BackendDriver {
|
||||||
github: {
|
github: {
|
||||||
listPullRequests,
|
listPullRequests,
|
||||||
createPr,
|
createPr,
|
||||||
|
starRepository,
|
||||||
},
|
},
|
||||||
sandboxAgent: {
|
sandboxAgent: {
|
||||||
createClient: (opts) => {
|
createClient: (opts) => {
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,18 @@ export async function createPr(repoPath: string, headBranch: string, title: stri
|
||||||
return { number, url };
|
return { number, url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function starRepository(repoFullName: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await execFileAsync("gh", ["api", "--method", "PUT", `user/starred/${repoFullName}`], {
|
||||||
|
maxBuffer: 1024 * 1024,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : `Failed to star GitHub repository ${repoFullName}. Ensure GitHub auth is configured for the backend.`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAllowedMergeMethod(repoPath: string): Promise<"squash" | "rebase" | "merge"> {
|
export async function getAllowedMergeMethod(repoPath: string): Promise<"squash" | "rebase" | "merge"> {
|
||||||
try {
|
try {
|
||||||
// Get the repo owner/name from gh
|
// Get the repo owner/name from gh
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ export function createTestGithubDriver(overrides?: Partial<GithubDriver>): Githu
|
||||||
number: 1,
|
number: 1,
|
||||||
url: `https://github.com/test/repo/pull/1`,
|
url: `https://github.com/test/repo/pull/1`,
|
||||||
}),
|
}),
|
||||||
|
starRepository: async () => {},
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { setupTest } from "rivetkit/test";
|
||||||
|
import { workspaceKey } from "../src/actors/keys.js";
|
||||||
|
import { registry } from "../src/actors/index.js";
|
||||||
|
import { createTestDriver } from "./helpers/test-driver.js";
|
||||||
|
import { createTestRuntimeContext } from "./helpers/test-context.js";
|
||||||
|
|
||||||
|
const runActorIntegration = process.env.HF_ENABLE_ACTOR_INTEGRATION_TESTS === "1";
|
||||||
|
|
||||||
|
describe("workspace star sandbox agent repo", () => {
|
||||||
|
it.skipIf(!runActorIntegration)("stars the sandbox agent repo through the github driver", async (t) => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const testDriver = createTestDriver({
|
||||||
|
github: {
|
||||||
|
listPullRequests: async () => [],
|
||||||
|
createPr: async () => ({
|
||||||
|
number: 1,
|
||||||
|
url: "https://github.com/test/repo/pull/1",
|
||||||
|
}),
|
||||||
|
starRepository: async (repoFullName) => {
|
||||||
|
calls.push(repoFullName);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createTestRuntimeContext(testDriver);
|
||||||
|
|
||||||
|
const { client } = await setupTest(t, registry);
|
||||||
|
const ws = await client.workspace.getOrCreate(workspaceKey("alpha"), {
|
||||||
|
createWithInput: "alpha",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await ws.starSandboxAgentRepo({ workspaceId: "alpha" });
|
||||||
|
|
||||||
|
expect(calls).toEqual(["rivet-dev/sandbox-agent"]);
|
||||||
|
expect(result.repo).toBe("rivet-dev/sandbox-agent");
|
||||||
|
expect(typeof result.starredAt).toBe("number");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -25,6 +25,8 @@ import type {
|
||||||
RepoStackActionInput,
|
RepoStackActionInput,
|
||||||
RepoStackActionResult,
|
RepoStackActionResult,
|
||||||
RepoRecord,
|
RepoRecord,
|
||||||
|
StarSandboxAgentRepoInput,
|
||||||
|
StarSandboxAgentRepoResult,
|
||||||
SwitchResult,
|
SwitchResult,
|
||||||
} from "@openhandoff/shared";
|
} from "@openhandoff/shared";
|
||||||
import { sandboxInstanceKey, workspaceKey } from "./keys.js";
|
import { sandboxInstanceKey, workspaceKey } from "./keys.js";
|
||||||
|
|
@ -76,6 +78,7 @@ interface WorkspaceHandle {
|
||||||
archiveHandoff(input: { workspaceId: string; handoffId: string; reason?: string }): Promise<void>;
|
archiveHandoff(input: { workspaceId: string; handoffId: string; reason?: string }): Promise<void>;
|
||||||
killHandoff(input: { workspaceId: string; handoffId: string; reason?: string }): Promise<void>;
|
killHandoff(input: { workspaceId: string; handoffId: string; reason?: string }): Promise<void>;
|
||||||
useWorkspace(input: { workspaceId: string }): Promise<{ workspaceId: string }>;
|
useWorkspace(input: { workspaceId: string }): Promise<{ workspaceId: string }>;
|
||||||
|
starSandboxAgentRepo(input: StarSandboxAgentRepoInput): Promise<StarSandboxAgentRepoResult>;
|
||||||
getWorkbench(input: { workspaceId: string }): Promise<HandoffWorkbenchSnapshot>;
|
getWorkbench(input: { workspaceId: string }): Promise<HandoffWorkbenchSnapshot>;
|
||||||
createWorkbenchHandoff(input: HandoffWorkbenchCreateHandoffInput): Promise<HandoffWorkbenchCreateHandoffResponse>;
|
createWorkbenchHandoff(input: HandoffWorkbenchCreateHandoffInput): Promise<HandoffWorkbenchCreateHandoffResponse>;
|
||||||
markWorkbenchUnread(input: HandoffWorkbenchSelectInput): Promise<void>;
|
markWorkbenchUnread(input: HandoffWorkbenchSelectInput): Promise<void>;
|
||||||
|
|
@ -197,6 +200,7 @@ export interface BackendClient {
|
||||||
revertWorkbenchFile(workspaceId: string, input: HandoffWorkbenchDiffInput): Promise<void>;
|
revertWorkbenchFile(workspaceId: string, input: HandoffWorkbenchDiffInput): Promise<void>;
|
||||||
health(): Promise<{ ok: true }>;
|
health(): Promise<{ ok: true }>;
|
||||||
useWorkspace(workspaceId: string): Promise<{ workspaceId: string }>;
|
useWorkspace(workspaceId: string): Promise<{ workspaceId: string }>;
|
||||||
|
starSandboxAgentRepo(workspaceId: string): Promise<StarSandboxAgentRepoResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rivetEndpoint(config: AppConfig): string {
|
export function rivetEndpoint(config: AppConfig): string {
|
||||||
|
|
@ -504,6 +508,10 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
||||||
return (await workspace(input.workspaceId)).createHandoff(input);
|
return (await workspace(input.workspaceId)).createHandoff(input);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async starSandboxAgentRepo(workspaceId: string): Promise<StarSandboxAgentRepoResult> {
|
||||||
|
return (await workspace(workspaceId)).starSandboxAgentRepo({ workspaceId });
|
||||||
|
},
|
||||||
|
|
||||||
async listHandoffs(workspaceId: string, repoId?: string): Promise<HandoffSummary[]> {
|
async listHandoffs(workspaceId: string, repoId?: string): Promise<HandoffSummary[]> {
|
||||||
return (await workspace(workspaceId)).listHandoffs({ workspaceId, repoId });
|
return (await workspace(workspaceId)).listHandoffs({ workspaceId, repoId });
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,11 @@ import {
|
||||||
type Message,
|
type Message,
|
||||||
type ModelId,
|
type ModelId,
|
||||||
} from "./mock-layout/view-model";
|
} from "./mock-layout/view-model";
|
||||||
|
import { backendClient } from "../lib/backend";
|
||||||
import { handoffWorkbenchClient } from "../lib/workbench";
|
import { handoffWorkbenchClient } from "../lib/workbench";
|
||||||
|
|
||||||
|
const STAR_SANDBOX_AGENT_REPO_STORAGE_KEY = "hf.onboarding.starSandboxAgentRepo";
|
||||||
|
|
||||||
function firstAgentTabId(handoff: Handoff): string | null {
|
function firstAgentTabId(handoff: Handoff): string | null {
|
||||||
return handoff.tabs[0]?.id ?? null;
|
return handoff.tabs[0]?.id ?? null;
|
||||||
}
|
}
|
||||||
|
|
@ -559,9 +562,23 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
|
||||||
const [activeTabIdByHandoff, setActiveTabIdByHandoff] = useState<Record<string, string | null>>({});
|
const [activeTabIdByHandoff, setActiveTabIdByHandoff] = useState<Record<string, string | null>>({});
|
||||||
const [lastAgentTabIdByHandoff, setLastAgentTabIdByHandoff] = useState<Record<string, string | null>>({});
|
const [lastAgentTabIdByHandoff, setLastAgentTabIdByHandoff] = useState<Record<string, string | null>>({});
|
||||||
const [openDiffsByHandoff, setOpenDiffsByHandoff] = useState<Record<string, string[]>>({});
|
const [openDiffsByHandoff, setOpenDiffsByHandoff] = useState<Record<string, string[]>>({});
|
||||||
|
const [starRepoPromptOpen, setStarRepoPromptOpen] = useState(false);
|
||||||
|
const [starRepoPending, setStarRepoPending] = useState(false);
|
||||||
|
const [starRepoError, setStarRepoError] = useState<string | null>(null);
|
||||||
|
|
||||||
const activeHandoff = useMemo(() => handoffs.find((handoff) => handoff.id === selectedHandoffId) ?? handoffs[0] ?? null, [handoffs, selectedHandoffId]);
|
const activeHandoff = useMemo(() => handoffs.find((handoff) => handoff.id === selectedHandoffId) ?? handoffs[0] ?? null, [handoffs, selectedHandoffId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const status = globalThis.localStorage?.getItem(STAR_SANDBOX_AGENT_REPO_STORAGE_KEY);
|
||||||
|
if (status !== "completed" && status !== "dismissed") {
|
||||||
|
setStarRepoPromptOpen(true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setStarRepoPromptOpen(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeHandoff) {
|
if (activeHandoff) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -798,105 +815,231 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
|
||||||
[activeHandoff, lastAgentTabIdByHandoff],
|
[activeHandoff, lastAgentTabIdByHandoff],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dismissStarRepoPrompt = useCallback(() => {
|
||||||
|
setStarRepoError(null);
|
||||||
|
try {
|
||||||
|
globalThis.localStorage?.setItem(STAR_SANDBOX_AGENT_REPO_STORAGE_KEY, "dismissed");
|
||||||
|
} catch {
|
||||||
|
// ignore storage failures
|
||||||
|
}
|
||||||
|
setStarRepoPromptOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const starSandboxAgentRepo = useCallback(() => {
|
||||||
|
setStarRepoPending(true);
|
||||||
|
setStarRepoError(null);
|
||||||
|
void backendClient
|
||||||
|
.starSandboxAgentRepo(workspaceId)
|
||||||
|
.then(() => {
|
||||||
|
try {
|
||||||
|
globalThis.localStorage?.setItem(STAR_SANDBOX_AGENT_REPO_STORAGE_KEY, "completed");
|
||||||
|
} catch {
|
||||||
|
// ignore storage failures
|
||||||
|
}
|
||||||
|
setStarRepoPromptOpen(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setStarRepoError(error instanceof Error ? error.message : String(error));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setStarRepoPending(false);
|
||||||
|
});
|
||||||
|
}, [workspaceId]);
|
||||||
|
|
||||||
|
const starRepoPrompt = starRepoPromptOpen ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
inset: 0,
|
||||||
|
zIndex: 10000,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "24px",
|
||||||
|
background: "rgba(0, 0, 0, 0.68)",
|
||||||
|
}}
|
||||||
|
data-testid="onboarding-star-repo-modal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "min(520px, 100%)",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.14)",
|
||||||
|
borderRadius: "18px",
|
||||||
|
background: "#111113",
|
||||||
|
boxShadow: "0 32px 80px rgba(0, 0, 0, 0.45)",
|
||||||
|
padding: "24px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||||
|
<div style={{ fontSize: "12px", letterSpacing: "0.08em", textTransform: "uppercase", color: "rgba(255, 255, 255, 0.5)" }}>Onboarding</div>
|
||||||
|
<h2 style={{ margin: 0, fontSize: "24px", lineHeight: 1.1 }}>Give us support for sandbox agent</h2>
|
||||||
|
<p style={{ margin: 0, color: "rgba(255, 255, 255, 0.72)", lineHeight: 1.5 }}>
|
||||||
|
Before you keep going, give us support for sandbox agent and star the repo right here in the app.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{starRepoError ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255, 110, 110, 0.32)",
|
||||||
|
background: "rgba(255, 110, 110, 0.08)",
|
||||||
|
padding: "12px 14px",
|
||||||
|
color: "#ffb4b4",
|
||||||
|
fontSize: "13px",
|
||||||
|
}}
|
||||||
|
data-testid="onboarding-star-repo-error"
|
||||||
|
>
|
||||||
|
{starRepoError}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", gap: "10px" }}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={dismissStarRepoPrompt}
|
||||||
|
style={{
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.14)",
|
||||||
|
borderRadius: "999px",
|
||||||
|
padding: "10px 16px",
|
||||||
|
background: "transparent",
|
||||||
|
color: "#e4e4e7",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Maybe later
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={starSandboxAgentRepo}
|
||||||
|
disabled={starRepoPending}
|
||||||
|
style={{
|
||||||
|
border: 0,
|
||||||
|
borderRadius: "999px",
|
||||||
|
padding: "10px 16px",
|
||||||
|
background: starRepoPending ? "#7f5539" : "#ff4f00",
|
||||||
|
color: "#fff",
|
||||||
|
cursor: starRepoPending ? "progress" : "pointer",
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
data-testid="onboarding-star-repo-submit"
|
||||||
|
>
|
||||||
|
{starRepoPending ? "Starring..." : "Star the sandbox agent repo"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
if (!activeHandoff) {
|
if (!activeHandoff) {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Shell>
|
||||||
|
<Sidebar
|
||||||
|
projects={projects}
|
||||||
|
activeId=""
|
||||||
|
onSelect={selectHandoff}
|
||||||
|
onCreate={createHandoff}
|
||||||
|
onMarkUnread={markHandoffUnread}
|
||||||
|
onRenameHandoff={renameHandoff}
|
||||||
|
onRenameBranch={renameBranch}
|
||||||
|
/>
|
||||||
|
<SPanel>
|
||||||
|
<ScrollBody>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minHeight: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "32px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: "420px",
|
||||||
|
textAlign: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ margin: 0, fontSize: "20px", fontWeight: 600 }}>Create your first handoff</h2>
|
||||||
|
<p style={{ margin: 0, opacity: 0.75 }}>
|
||||||
|
{viewModel.repos.length > 0
|
||||||
|
? "Start from the sidebar to create a handoff on the first available repo."
|
||||||
|
: "No repos are available in this workspace yet."}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={createHandoff}
|
||||||
|
disabled={viewModel.repos.length === 0}
|
||||||
|
style={{
|
||||||
|
alignSelf: "center",
|
||||||
|
border: 0,
|
||||||
|
borderRadius: "999px",
|
||||||
|
padding: "10px 18px",
|
||||||
|
background: viewModel.repos.length > 0 ? "#ff4f00" : "#444",
|
||||||
|
color: "#fff",
|
||||||
|
cursor: viewModel.repos.length > 0 ? "pointer" : "not-allowed",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New handoff
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollBody>
|
||||||
|
</SPanel>
|
||||||
|
<SPanel />
|
||||||
|
</Shell>
|
||||||
|
{starRepoPrompt}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<Shell>
|
<Shell>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
projects={projects}
|
projects={projects}
|
||||||
activeId=""
|
activeId={activeHandoff.id}
|
||||||
onSelect={selectHandoff}
|
onSelect={selectHandoff}
|
||||||
onCreate={createHandoff}
|
onCreate={createHandoff}
|
||||||
onMarkUnread={markHandoffUnread}
|
onMarkUnread={markHandoffUnread}
|
||||||
onRenameHandoff={renameHandoff}
|
onRenameHandoff={renameHandoff}
|
||||||
onRenameBranch={renameBranch}
|
onRenameBranch={renameBranch}
|
||||||
/>
|
/>
|
||||||
<SPanel>
|
<TranscriptPanel
|
||||||
<ScrollBody>
|
handoff={activeHandoff}
|
||||||
<div
|
activeTabId={activeTabId}
|
||||||
style={{
|
lastAgentTabId={lastAgentTabId}
|
||||||
minHeight: "100%",
|
openDiffs={openDiffs}
|
||||||
display: "flex",
|
onSyncRouteSession={syncRouteSession}
|
||||||
alignItems: "center",
|
onSetActiveTabId={(tabId) => {
|
||||||
justifyContent: "center",
|
setActiveTabIdByHandoff((current) => ({ ...current, [activeHandoff.id]: tabId }));
|
||||||
padding: "32px",
|
}}
|
||||||
}}
|
onSetLastAgentTabId={(tabId) => {
|
||||||
>
|
setLastAgentTabIdByHandoff((current) => ({ ...current, [activeHandoff.id]: tabId }));
|
||||||
<div
|
}}
|
||||||
style={{
|
onSetOpenDiffs={(paths) => {
|
||||||
maxWidth: "420px",
|
setOpenDiffsByHandoff((current) => ({ ...current, [activeHandoff.id]: paths }));
|
||||||
textAlign: "center",
|
}}
|
||||||
display: "flex",
|
/>
|
||||||
flexDirection: "column",
|
<RightSidebar
|
||||||
gap: "12px",
|
handoff={activeHandoff}
|
||||||
}}
|
activeTabId={activeTabId}
|
||||||
>
|
onOpenDiff={openDiffTab}
|
||||||
<h2 style={{ margin: 0, fontSize: "20px", fontWeight: 600 }}>Create your first handoff</h2>
|
onArchive={archiveHandoff}
|
||||||
<p style={{ margin: 0, opacity: 0.75 }}>
|
onRevertFile={revertFile}
|
||||||
{viewModel.repos.length > 0
|
onPublishPr={publishPr}
|
||||||
? "Start from the sidebar to create a handoff on the first available repo."
|
/>
|
||||||
: "No repos are available in this workspace yet."}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={createHandoff}
|
|
||||||
disabled={viewModel.repos.length === 0}
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
border: 0,
|
|
||||||
borderRadius: "999px",
|
|
||||||
padding: "10px 18px",
|
|
||||||
background: viewModel.repos.length > 0 ? "#ff4f00" : "#444",
|
|
||||||
color: "#fff",
|
|
||||||
cursor: viewModel.repos.length > 0 ? "pointer" : "not-allowed",
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
New handoff
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ScrollBody>
|
|
||||||
</SPanel>
|
|
||||||
<SPanel />
|
|
||||||
</Shell>
|
</Shell>
|
||||||
);
|
{starRepoPrompt}
|
||||||
}
|
</>
|
||||||
|
|
||||||
return (
|
|
||||||
<Shell>
|
|
||||||
<Sidebar
|
|
||||||
projects={projects}
|
|
||||||
activeId={activeHandoff.id}
|
|
||||||
onSelect={selectHandoff}
|
|
||||||
onCreate={createHandoff}
|
|
||||||
onMarkUnread={markHandoffUnread}
|
|
||||||
onRenameHandoff={renameHandoff}
|
|
||||||
onRenameBranch={renameBranch}
|
|
||||||
/>
|
|
||||||
<TranscriptPanel
|
|
||||||
handoff={activeHandoff}
|
|
||||||
activeTabId={activeTabId}
|
|
||||||
lastAgentTabId={lastAgentTabId}
|
|
||||||
openDiffs={openDiffs}
|
|
||||||
onSyncRouteSession={syncRouteSession}
|
|
||||||
onSetActiveTabId={(tabId) => {
|
|
||||||
setActiveTabIdByHandoff((current) => ({ ...current, [activeHandoff.id]: tabId }));
|
|
||||||
}}
|
|
||||||
onSetLastAgentTabId={(tabId) => {
|
|
||||||
setLastAgentTabIdByHandoff((current) => ({ ...current, [activeHandoff.id]: tabId }));
|
|
||||||
}}
|
|
||||||
onSetOpenDiffs={(paths) => {
|
|
||||||
setOpenDiffsByHandoff((current) => ({ ...current, [activeHandoff.id]: paths }));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RightSidebar
|
|
||||||
handoff={activeHandoff}
|
|
||||||
activeTabId={activeTabId}
|
|
||||||
onOpenDiff={openDiffTab}
|
|
||||||
onArchive={archiveHandoff}
|
|
||||||
onRevertFile={revertFile}
|
|
||||||
onPublishPr={publishPr}
|
|
||||||
/>
|
|
||||||
</Shell>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,17 @@ export const WorkspaceUseInputSchema = z.object({
|
||||||
});
|
});
|
||||||
export type WorkspaceUseInput = z.infer<typeof WorkspaceUseInputSchema>;
|
export type WorkspaceUseInput = z.infer<typeof WorkspaceUseInputSchema>;
|
||||||
|
|
||||||
|
export const StarSandboxAgentRepoInputSchema = z.object({
|
||||||
|
workspaceId: WorkspaceIdSchema,
|
||||||
|
});
|
||||||
|
export type StarSandboxAgentRepoInput = z.infer<typeof StarSandboxAgentRepoInputSchema>;
|
||||||
|
|
||||||
|
export const StarSandboxAgentRepoResultSchema = z.object({
|
||||||
|
repo: z.string().min(1),
|
||||||
|
starredAt: z.number().int(),
|
||||||
|
});
|
||||||
|
export type StarSandboxAgentRepoResult = z.infer<typeof StarSandboxAgentRepoResultSchema>;
|
||||||
|
|
||||||
export const HistoryQueryInputSchema = z.object({
|
export const HistoryQueryInputSchema = z.object({
|
||||||
workspaceId: WorkspaceIdSchema,
|
workspaceId: WorkspaceIdSchema,
|
||||||
limit: z.number().int().positive().max(500).optional(),
|
limit: z.number().int().positive().max(500).optional(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue