mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 06:01:21 +00:00
Fix Foundry UI bugs: org names, hanging sessions, and wrong repo creation
- Fix org display name using GitHub description instead of name field - Fix createWorkbenchSession hanging when sandbox is provisioning - Fix auto-session creation retry storm on errors - Fix task creation using wrong repo due to React state race conditions - Remove Bun hot-reload from backend Dockerfile (causes port drift) - Add GitHub sync/install status to dev panel Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
14d5413f8a
commit
689d968397
17 changed files with 2569 additions and 479 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { injectMockLatency } from "./mock/latency.js";
|
||||
import rivetDevFixture from "../../../scripts/data/rivet-dev.json" with { type: "json" };
|
||||
|
||||
export type MockBillingPlanId = "free" | "team";
|
||||
export type MockBillingStatus = "active" | "trialing" | "past_due" | "scheduled_cancel";
|
||||
|
|
@ -140,6 +141,69 @@ function syncStatusFromLegacy(value: unknown): MockGithubSyncStatus {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the "rivet" mock organization from real public GitHub data.
|
||||
* Fixture sourced from: scripts/pull-org-data.ts (run against rivet-dev).
|
||||
* Members that don't exist in the public fixture get synthetic entries
|
||||
* so the mock still has realistic owner/admin/member role distribution.
|
||||
*/
|
||||
function buildRivetOrganization(): MockFoundryOrganization {
|
||||
const repos = rivetDevFixture.repos.map((r) => r.fullName);
|
||||
const fixtureMembers: MockFoundryOrganizationMember[] = rivetDevFixture.members.map((m) => ({
|
||||
id: `member-rivet-${m.login.toLowerCase()}`,
|
||||
name: m.login,
|
||||
email: `${m.login.toLowerCase()}@rivet.dev`,
|
||||
role: "member" as const,
|
||||
state: "active" as const,
|
||||
}));
|
||||
|
||||
// Ensure we have named owner/admin roles for the mock user personas
|
||||
// that may not appear in the public members list
|
||||
const knownMembers: MockFoundryOrganizationMember[] = [
|
||||
{ id: "member-rivet-jamie", name: "Jamie", email: "jamie@rivet.dev", role: "owner", state: "active" },
|
||||
{ id: "member-rivet-nathan", name: "Nathan", email: "nathan@acme.dev", role: "member", state: "active" },
|
||||
];
|
||||
|
||||
// Merge: known members take priority, then fixture members not already covered
|
||||
const knownIds = new Set(knownMembers.map((m) => m.id));
|
||||
const members = [...knownMembers, ...fixtureMembers.filter((m) => !knownIds.has(m.id))];
|
||||
|
||||
return {
|
||||
id: "rivet",
|
||||
workspaceId: "rivet",
|
||||
kind: "organization",
|
||||
settings: {
|
||||
displayName: rivetDevFixture.name ?? rivetDevFixture.login,
|
||||
slug: "rivet",
|
||||
primaryDomain: "rivet.dev",
|
||||
seatAccrualMode: "first_prompt",
|
||||
defaultModel: "o3",
|
||||
autoImportRepos: true,
|
||||
},
|
||||
github: {
|
||||
connectedAccount: rivetDevFixture.login,
|
||||
installationStatus: "connected",
|
||||
syncStatus: "synced",
|
||||
importedRepoCount: repos.length,
|
||||
lastSyncLabel: "Synced just now",
|
||||
lastSyncAt: Date.now() - 60_000,
|
||||
},
|
||||
billing: {
|
||||
planId: "team",
|
||||
status: "trialing",
|
||||
seatsIncluded: 5,
|
||||
trialEndsAt: isoDate(12),
|
||||
renewalAt: isoDate(12),
|
||||
stripeCustomerId: "cus_mock_rivet_team",
|
||||
paymentMethodLabel: "Visa ending in 4242",
|
||||
invoices: [{ id: "inv-rivet-001", label: "Team pilot", issuedAt: "2026-03-04", amountUsd: 0, status: "paid" }],
|
||||
},
|
||||
members,
|
||||
seatAssignments: ["jamie@rivet.dev"],
|
||||
repoCatalog: repos,
|
||||
};
|
||||
}
|
||||
|
||||
function buildDefaultSnapshot(): MockFoundryAppSnapshot {
|
||||
return {
|
||||
auth: {
|
||||
|
|
@ -259,44 +323,7 @@ function buildDefaultSnapshot(): MockFoundryAppSnapshot {
|
|||
seatAssignments: ["nathan@acme.dev", "maya@acme.dev"],
|
||||
repoCatalog: ["acme/backend", "acme/frontend", "acme/infra"],
|
||||
},
|
||||
{
|
||||
id: "rivet",
|
||||
workspaceId: "rivet",
|
||||
kind: "organization",
|
||||
settings: {
|
||||
displayName: "Rivet",
|
||||
slug: "rivet",
|
||||
primaryDomain: "rivet.dev",
|
||||
seatAccrualMode: "first_prompt",
|
||||
defaultModel: "o3",
|
||||
autoImportRepos: true,
|
||||
},
|
||||
github: {
|
||||
connectedAccount: "rivet-dev",
|
||||
installationStatus: "reconnect_required",
|
||||
syncStatus: "error",
|
||||
importedRepoCount: 4,
|
||||
lastSyncLabel: "Sync stalled 2 hours ago",
|
||||
lastSyncAt: Date.now() - 2 * 60 * 60_000,
|
||||
},
|
||||
billing: {
|
||||
planId: "team",
|
||||
status: "trialing",
|
||||
seatsIncluded: 5,
|
||||
trialEndsAt: isoDate(12),
|
||||
renewalAt: isoDate(12),
|
||||
stripeCustomerId: "cus_mock_rivet_team",
|
||||
paymentMethodLabel: "Visa ending in 4242",
|
||||
invoices: [{ id: "inv-rivet-001", label: "Team pilot", issuedAt: "2026-03-04", amountUsd: 0, status: "paid" }],
|
||||
},
|
||||
members: [
|
||||
{ id: "member-rivet-jamie", name: "Jamie", email: "jamie@rivet.dev", role: "owner", state: "active" },
|
||||
{ id: "member-rivet-nathan", name: "Nathan", email: "nathan@acme.dev", role: "member", state: "active" },
|
||||
{ id: "member-rivet-lena", name: "Lena", email: "lena@rivet.dev", role: "admin", state: "active" },
|
||||
],
|
||||
seatAssignments: ["jamie@rivet.dev"],
|
||||
repoCatalog: ["rivet/dashboard", "rivet/agents", "rivet/billing", "rivet/infrastructure"],
|
||||
},
|
||||
buildRivetOrganization(),
|
||||
{
|
||||
id: "personal-jamie",
|
||||
workspaceId: "personal-jamie",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
WorkbenchRepo,
|
||||
WorkbenchTranscriptEvent as TranscriptEvent,
|
||||
} from "@sandbox-agent/foundry-shared";
|
||||
import rivetDevFixture from "../../../scripts/data/rivet-dev.json" with { type: "json" };
|
||||
|
||||
export const MODEL_GROUPS: ModelGroup[] = [
|
||||
{
|
||||
|
|
@ -801,13 +802,13 @@ export function buildInitialTasks(): Task[] {
|
|||
fileTree: [],
|
||||
minutesUsed: 312,
|
||||
},
|
||||
// ── rivet-dev/cloud ──
|
||||
// ── rivet-dev/vbare ──
|
||||
{
|
||||
id: "h6",
|
||||
repoId: "cloud",
|
||||
repoId: "vbare",
|
||||
title: "Use full cloud run pool name for routing",
|
||||
status: "idle",
|
||||
repoName: "rivet-dev/cloud",
|
||||
repoName: "rivet-dev/vbare",
|
||||
updatedAtMs: minutesAgo(25),
|
||||
branch: "fix-use-full-cloud-run-pool-name",
|
||||
pullRequest: { number: 235, status: "ready" },
|
||||
|
|
@ -910,13 +911,13 @@ export function buildInitialTasks(): Task[] {
|
|||
],
|
||||
minutesUsed: 0,
|
||||
},
|
||||
// ── rivet-dev/engine-ee ──
|
||||
// ── rivet-dev/skills ──
|
||||
{
|
||||
id: "h7",
|
||||
repoId: "engine-ee",
|
||||
repoId: "skills",
|
||||
title: "Route compute gateway path correctly",
|
||||
status: "idle",
|
||||
repoName: "rivet-dev/engine-ee",
|
||||
repoName: "rivet-dev/skills",
|
||||
updatedAtMs: minutesAgo(50),
|
||||
branch: "fix-guard-support-https-targets",
|
||||
pullRequest: { number: 125, status: "ready" },
|
||||
|
|
@ -1024,13 +1025,13 @@ export function buildInitialTasks(): Task[] {
|
|||
],
|
||||
minutesUsed: 78,
|
||||
},
|
||||
// ── rivet-dev/engine-ee (archived) ──
|
||||
// ── rivet-dev/skills (archived) ──
|
||||
{
|
||||
id: "h8",
|
||||
repoId: "engine-ee",
|
||||
repoId: "skills",
|
||||
title: "Move compute gateway to guard",
|
||||
status: "archived",
|
||||
repoName: "rivet-dev/engine-ee",
|
||||
repoName: "rivet-dev/skills",
|
||||
updatedAtMs: minutesAgo(2 * 24 * 60),
|
||||
branch: "chore-move-compute-gateway-to",
|
||||
pullRequest: { number: 123, status: "ready" },
|
||||
|
|
@ -1066,13 +1067,13 @@ export function buildInitialTasks(): Task[] {
|
|||
fileTree: [],
|
||||
minutesUsed: 15,
|
||||
},
|
||||
// ── rivet-dev/secure-exec ──
|
||||
// ── rivet-dev/deploy-action ──
|
||||
{
|
||||
id: "h9",
|
||||
repoId: "secure-exec",
|
||||
repoId: "deploy-action",
|
||||
title: "Harden namespace isolation for nested containers",
|
||||
status: "idle",
|
||||
repoName: "rivet-dev/secure-exec",
|
||||
repoName: "rivet-dev/deploy-action",
|
||||
updatedAtMs: minutesAgo(90),
|
||||
branch: "fix/namespace-isolation",
|
||||
pullRequest: null,
|
||||
|
|
@ -1122,15 +1123,63 @@ export function buildInitialTasks(): Task[] {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build repos list from the rivet-dev fixture data (scripts/data/rivet-dev.json).
|
||||
* Uses real public repos so the mock sidebar matches what an actual rivet-dev
|
||||
* workspace would show after a GitHub sync.
|
||||
*/
|
||||
function buildMockRepos(): WorkbenchRepo[] {
|
||||
return rivetDevFixture.repos.map((r) => ({
|
||||
id: repoIdFromFullName(r.fullName),
|
||||
label: r.fullName,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Derive a stable short id from a "org/repo" full name (e.g. "rivet-dev/rivet" → "rivet"). */
|
||||
function repoIdFromFullName(fullName: string): string {
|
||||
const parts = fullName.split("/");
|
||||
return parts[parts.length - 1] ?? fullName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build task entries from open PR fixture data.
|
||||
* Maps to the backend's PR sync behavior (ProjectPrSyncActor) where PRs
|
||||
* appear as first-class sidebar items even without an associated task.
|
||||
* Each open PR gets a lightweight task entry so it shows in the sidebar.
|
||||
*/
|
||||
function buildPrTasks(): Task[] {
|
||||
// Collect branch names already claimed by hand-written tasks so we don't duplicate
|
||||
const existingBranches = new Set(
|
||||
buildInitialTasks()
|
||||
.map((t) => t.branch)
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
return rivetDevFixture.openPullRequests
|
||||
.filter((pr) => !existingBranches.has(pr.headRefName))
|
||||
.map((pr) => {
|
||||
const repoId = repoIdFromFullName(pr.repoFullName);
|
||||
return {
|
||||
id: `pr-${repoId}-${pr.number}`,
|
||||
repoId,
|
||||
title: pr.title,
|
||||
status: "idle" as const,
|
||||
repoName: pr.repoFullName,
|
||||
updatedAtMs: new Date(pr.updatedAt).getTime(),
|
||||
branch: pr.headRefName,
|
||||
pullRequest: { number: pr.number, status: pr.draft ? ("draft" as const) : ("ready" as const) },
|
||||
tabs: [],
|
||||
fileChanges: [],
|
||||
diffs: {},
|
||||
fileTree: [],
|
||||
minutesUsed: 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildInitialMockLayoutViewModel(): TaskWorkbenchSnapshot {
|
||||
const repos: WorkbenchRepo[] = [
|
||||
{ id: "sandbox-agent", label: "rivet-dev/sandbox-agent" },
|
||||
{ id: "rivet", label: "rivet-dev/rivet" },
|
||||
{ id: "cloud", label: "rivet-dev/cloud" },
|
||||
{ id: "engine-ee", label: "rivet-dev/engine-ee" },
|
||||
{ id: "secure-exec", label: "rivet-dev/secure-exec" },
|
||||
];
|
||||
const tasks = buildInitialTasks();
|
||||
const repos = buildMockRepos();
|
||||
const tasks = [...buildInitialTasks(), ...buildPrTasks()];
|
||||
return {
|
||||
workspaceId: "default",
|
||||
repos,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue