diff --git a/factory/CLAUDE.md b/factory/CLAUDE.md index 7755510..0454f02 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. @@ -163,6 +168,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 ccdbb60..c800533 100644 --- a/factory/packages/backend/src/actors/handoff/index.ts +++ b/factory/packages/backend/src/actors/handoff/index.ts @@ -47,6 +47,7 @@ export interface HandoffInput { agentType: AgentType | null; explicitTitle: string | null; explicitBranchName: string | null; + initialPrompt: string | null; } interface InitializeCommand { @@ -125,6 +126,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 c05ee86..c8c6d0c 100644 --- a/factory/packages/backend/src/actors/handoff/workflow/init.ts +++ b/factory/packages/backend/src/actors/handoff/workflow/init.ts @@ -370,7 +370,10 @@ export async function initCreateSessionActivity(loopCtx: any, body: any, sandbox const cwd = sandbox.metadata && typeof (sandbox.metadata as any).cwd === "string" ? ((sandbox.metadata as any).cwd as string) : 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 5ff4b47..440ed06 100644 --- a/factory/packages/backend/src/actors/project/actions.ts +++ b/factory/packages/backend/src/actors/project/actions.ts @@ -28,6 +28,7 @@ interface CreateHandoffCommand { agentType: AgentType | null; explicitTitle: string | null; explicitBranchName: string | null; + initialPrompt: string | null; onBranch: string | null; } @@ -365,6 +366,7 @@ async function createHandoffMutation(c: any, cmd: CreateHandoffCommand): Promise agentType: cmd.agentType, explicitTitle: onBranch ? null : cmd.explicitTitle, 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 e1785a6..9fe69eb 100644 --- a/factory/packages/backend/src/actors/workspace/actions.ts +++ b/factory/packages/backend/src/actors/workspace/actions.ts @@ -282,6 +282,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, }); @@ -299,9 +300,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 { @@ -437,16 +439,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 e06683b..19768dd 100644 --- a/factory/packages/frontend/src/components/mock-layout.tsx +++ b/factory/packages/frontend/src/components/mock-layout.tsx @@ -949,19 +949,13 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } throw new Error("Cannot create a task without an available repo"); } - const task = window.prompt("Describe the task", "Investigate and implement the requested change"); - if (!task) { - return; - } - - const title = window.prompt("Optional task 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 a020674..9722c3b 100644 --- a/factory/packages/shared/src/contracts.ts +++ b/factory/packages/shared/src/contracts.ts @@ -65,6 +65,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 {