From 5e733e8b379fedc7d368a48d7ce6a00626840ee6 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 11 Mar 2026 11:06:51 -0700 Subject: [PATCH] Fix Foundry handoff creation flow --- docs/openapi.json | 2 +- factory/CLAUDE.md | 6 ++++++ factory/packages/backend/src/actors/handoff/index.ts | 2 ++ .../backend/src/actors/handoff/workflow/init.ts | 5 ++++- .../packages/backend/src/actors/project/actions.ts | 4 +++- .../packages/backend/src/actors/workspace/actions.ts | 12 +++++++++--- .../packages/frontend/src/components/mock-layout.tsx | 12 +++--------- factory/packages/shared/src/contracts.ts | 1 + factory/packages/shared/src/workbench.ts | 1 + 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index b399f74..5735c51 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -10,7 +10,7 @@ "license": { "name": "Apache-2.0" }, - "version": "0.3.0" + "version": "0.3.1" }, "servers": [ { diff --git a/factory/CLAUDE.md b/factory/CLAUDE.md index 17142ca..ee05893 100644 --- a/factory/CLAUDE.md +++ b/factory/CLAUDE.md @@ -44,6 +44,11 @@ Use `pnpm` workspaces and Turborepo. - Stop the preview stack: `just factory-preview-down` - Tail preview logs: `just factory-preview-logs` +## Local Env + +- For local The Foundry dev server setup, keep a personal env copy at `~/misc/the-foundry.env`. +- To run the dev server from this workspace, copy that content into the repo root `.env`. Root `.env` is gitignored in this repo, so keep local secrets there and do not commit them. + ## Frontend + Client Boundary - Keep a browser-friendly GUI implementation aligned with the TUI interaction model wherever possible. @@ -161,6 +166,7 @@ For all Rivet/RivetKit implementation: - Integration tests use `setupTest()` from `rivetkit/test` and are gated behind `HF_ENABLE_ACTOR_INTEGRATION_TESTS=1`. - End-to-end testing must run against the dev backend started via `docker compose -f compose.dev.yaml up` (host -> container). Do not run E2E against an in-process test runtime. - E2E tests should talk to the backend over HTTP (default `http://127.0.0.1:7741/api/rivet`) and use real GitHub repos/PRs. + - Current org test repo: `rivet-dev/sandbox-agent-testing` (`https://github.com/rivet-dev/sandbox-agent-testing`). - Secrets (e.g. `OPENAI_API_KEY`, `GITHUB_TOKEN`/`GH_TOKEN`) must be provided via environment variables, never hardcoded in the repo. - Treat client E2E tests in `packages/client/test` as the primary end-to-end source of truth for product behavior. - Keep backend tests small and targeted. Only retain backend-only tests for invariants or persistence rules that are not well-covered through client E2E. diff --git a/factory/packages/backend/src/actors/handoff/index.ts b/factory/packages/backend/src/actors/handoff/index.ts index 2ad52f7..81a6e6c 100644 --- a/factory/packages/backend/src/actors/handoff/index.ts +++ b/factory/packages/backend/src/actors/handoff/index.ts @@ -51,6 +51,7 @@ export interface HandoffInput { agentType: AgentType | null; explicitTitle: string | null; explicitBranchName: string | null; + initialPrompt: string | null; } interface InitializeCommand { @@ -129,6 +130,7 @@ export const handoff = actor({ agentType: input.agentType, explicitTitle: input.explicitTitle, explicitBranchName: input.explicitBranchName, + initialPrompt: input.initialPrompt, initialized: false, previousStatus: null as string | null, }), diff --git a/factory/packages/backend/src/actors/handoff/workflow/init.ts b/factory/packages/backend/src/actors/handoff/workflow/init.ts index f413578..24663e0 100644 --- a/factory/packages/backend/src/actors/handoff/workflow/init.ts +++ b/factory/packages/backend/src/actors/handoff/workflow/init.ts @@ -413,7 +413,10 @@ export async function initCreateSessionActivity( : undefined; return await sandboxInstance.createSession({ - prompt: buildAgentPrompt(loopCtx.state.task), + prompt: + typeof loopCtx.state.initialPrompt === "string" + ? loopCtx.state.initialPrompt + : buildAgentPrompt(loopCtx.state.task), cwd, agent: (loopCtx.state.agentType ?? config.default_agent) as any }); diff --git a/factory/packages/backend/src/actors/project/actions.ts b/factory/packages/backend/src/actors/project/actions.ts index d0fc978..f05c2b2 100644 --- a/factory/packages/backend/src/actors/project/actions.ts +++ b/factory/packages/backend/src/actors/project/actions.ts @@ -43,6 +43,7 @@ interface CreateHandoffCommand { agentType: AgentType | null; explicitTitle: string | null; explicitBranchName: string | null; + initialPrompt: string | null; onBranch: string | null; } @@ -387,7 +388,8 @@ async function createHandoffMutation(c: any, cmd: CreateHandoffCommand): Promise providerId: cmd.providerId, agentType: cmd.agentType, explicitTitle: onBranch ? null : cmd.explicitTitle, - explicitBranchName: onBranch ? null : cmd.explicitBranchName + explicitBranchName: onBranch ? null : cmd.explicitBranchName, + initialPrompt: cmd.initialPrompt, }); } catch (error) { if (onBranch) { diff --git a/factory/packages/backend/src/actors/workspace/actions.ts b/factory/packages/backend/src/actors/workspace/actions.ts index 93acf16..5584a4f 100644 --- a/factory/packages/backend/src/actors/workspace/actions.ts +++ b/factory/packages/backend/src/actors/workspace/actions.ts @@ -295,6 +295,7 @@ async function createHandoffMutation(c: any, input: CreateHandoffInput): Promise agentType: input.agentType ?? null, explicitTitle: input.explicitTitle ?? null, explicitBranchName: input.explicitBranchName ?? null, + initialPrompt: input.initialPrompt ?? null, onBranch: input.onBranch ?? null }); @@ -312,9 +313,10 @@ async function createHandoffMutation(c: any, input: CreateHandoffInput): Promise const handoff = getHandoff(c, c.state.workspaceId, repoId, created.handoffId); await handoff.provision({ providerId }); + const provisioned = await handoff.get(); await workspaceActions.notifyWorkbenchUpdated(c); - return created; + return provisioned; } async function refreshProviderProfilesMutation(c: any, command?: RefreshProviderProfilesCommand): Promise { @@ -440,16 +442,20 @@ export const workspaceActions = { c.broadcast("workbenchUpdated", { at: Date.now() }); }, - async createWorkbenchHandoff(c: any, input: HandoffWorkbenchCreateHandoffInput): Promise<{ handoffId: string }> { + async createWorkbenchHandoff(c: any, input: HandoffWorkbenchCreateHandoffInput): Promise<{ handoffId: string; tabId?: string }> { const created = await workspaceActions.createHandoff(c, { workspaceId: c.state.workspaceId, repoId: input.repoId, task: input.task, ...(input.title ? { explicitTitle: input.title } : {}), ...(input.branch ? { explicitBranchName: input.branch } : {}), + ...(input.initialPrompt !== undefined ? { initialPrompt: input.initialPrompt } : {}), ...(input.model ? { agentType: agentTypeForModel(input.model) } : {}) }); - return { handoffId: created.handoffId }; + return { + handoffId: created.handoffId, + ...(created.activeSessionId ? { tabId: created.activeSessionId } : {}), + }; }, async markWorkbenchUnread(c: any, input: HandoffWorkbenchSelectInput): Promise { diff --git a/factory/packages/frontend/src/components/mock-layout.tsx b/factory/packages/frontend/src/components/mock-layout.tsx index 68dd14f..27b447f 100644 --- a/factory/packages/frontend/src/components/mock-layout.tsx +++ b/factory/packages/frontend/src/components/mock-layout.tsx @@ -661,19 +661,13 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } throw new Error("Cannot create a handoff without an available repo"); } - const task = window.prompt("Describe the handoff task", "Investigate and implement the requested change"); - if (!task) { - return; - } - - const title = window.prompt("Optional handoff title", "")?.trim() || undefined; - const branch = window.prompt("Optional branch name", "")?.trim() || undefined; + const task = "New task"; const { handoffId, tabId } = await handoffWorkbenchClient.createHandoff({ repoId, task, + title: task, model: "gpt-4o", - ...(title ? { title } : {}), - ...(branch ? { branch } : {}), + initialPrompt: "", }); await navigate({ to: "/workspaces/$workspaceId/handoffs/$handoffId", diff --git a/factory/packages/shared/src/contracts.ts b/factory/packages/shared/src/contracts.ts index a1bc7ee..20b7049 100644 --- a/factory/packages/shared/src/contracts.ts +++ b/factory/packages/shared/src/contracts.ts @@ -61,6 +61,7 @@ export const CreateHandoffInputSchema = z.object({ task: z.string().min(1), explicitTitle: z.string().trim().min(1).optional(), explicitBranchName: z.string().trim().min(1).optional(), + initialPrompt: z.string().optional(), providerId: ProviderIdSchema.optional(), agentType: AgentTypeSchema.optional(), onBranch: z.string().trim().min(1).optional() diff --git a/factory/packages/shared/src/workbench.ts b/factory/packages/shared/src/workbench.ts index 8e26220..683c372 100644 --- a/factory/packages/shared/src/workbench.ts +++ b/factory/packages/shared/src/workbench.ts @@ -130,6 +130,7 @@ export interface HandoffWorkbenchCreateHandoffInput { title?: string; branch?: string; model?: WorkbenchModelId; + initialPrompt?: string; } export interface HandoffWorkbenchRenameInput {