From aa332307e58e939eb87e9feced15770ec4963519 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sun, 15 Mar 2026 13:43:24 -0700 Subject: [PATCH] Fix Foundry validation fallout --- .../packages/backend/src/actors/github-data/index.ts | 1 + .../backend/src/actors/organization/app-shell.ts | 1 + foundry/packages/backend/src/actors/task/workspace.ts | 1 + foundry/packages/client/src/backend-client.ts | 5 +++-- foundry/packages/client/src/mock/workspace-client.ts | 1 + foundry/packages/client/src/workspace-model.ts | 1 + .../client/test/e2e/full-integration-e2e.test.ts | 4 ++-- .../packages/client/test/e2e/workspace-e2e.test.ts | 11 ++++++++++- .../client/test/e2e/workspace-load-e2e.test.ts | 2 ++ foundry/packages/shared/src/contracts.ts | 1 + foundry/packages/shared/src/logging.ts | 4 ++-- 11 files changed, 25 insertions(+), 7 deletions(-) diff --git a/foundry/packages/backend/src/actors/github-data/index.ts b/foundry/packages/backend/src/actors/github-data/index.ts index ebc453f..1fd1a64 100644 --- a/foundry/packages/backend/src/actors/github-data/index.ts +++ b/foundry/packages/backend/src/actors/github-data/index.ts @@ -138,6 +138,7 @@ function pullRequestSummaryFromRow(row: any) { repoId: row.repoId, repoFullName: row.repoFullName, number: row.number, + status: Boolean(row.isDraft) ? "draft" : "ready", title: row.title, state: row.state, url: row.url, diff --git a/foundry/packages/backend/src/actors/organization/app-shell.ts b/foundry/packages/backend/src/actors/organization/app-shell.ts index a21cd7b..1522685 100644 --- a/foundry/packages/backend/src/actors/organization/app-shell.ts +++ b/foundry/packages/backend/src/actors/organization/app-shell.ts @@ -1089,6 +1089,7 @@ export const organizationAppActions = { }, pullRequest: { number: body.pull_request.number, + status: body.pull_request.draft ? "draft" : "ready", title: body.pull_request.title ?? "", body: body.pull_request.body ?? null, state: body.pull_request.state ?? "open", diff --git a/foundry/packages/backend/src/actors/task/workspace.ts b/foundry/packages/backend/src/actors/task/workspace.ts index ee44502..40bc22e 100644 --- a/foundry/packages/backend/src/actors/task/workspace.ts +++ b/foundry/packages/backend/src/actors/task/workspace.ts @@ -1378,6 +1378,7 @@ export async function publishWorkspacePr(c: any): Promise { }); await syncTaskPullRequest(c, { number: created.number, + status: "ready", title: record.title ?? record.task, body: null, state: "open", diff --git a/foundry/packages/client/src/backend-client.ts b/foundry/packages/client/src/backend-client.ts index f9a3f66..a0ff36c 100644 --- a/foundry/packages/client/src/backend-client.ts +++ b/foundry/packages/client/src/backend-client.ts @@ -7,6 +7,7 @@ import type { CreateTaskInput, AppEvent, SessionEvent, + SandboxProcessSnapshot, SandboxProcessesEvent, TaskRecord, TaskSummary, @@ -40,7 +41,7 @@ import type { WorkspaceModelGroup, WorkspaceModelId, } from "@sandbox-agent/foundry-shared"; -import type { ProcessCreateRequest, ProcessInfo, ProcessLogFollowQuery, ProcessLogsResponse, ProcessSignalQuery } from "sandbox-agent"; +import type { ProcessCreateRequest, ProcessLogFollowQuery, ProcessLogsResponse, ProcessSignalQuery } from "sandbox-agent"; import { createMockBackendClient } from "./mock/backend-client.js"; import { taskKey, taskSandboxKey, organizationKey } from "./keys.js"; @@ -66,7 +67,7 @@ export interface SandboxSessionEventRecord { payload: unknown; } -export type SandboxProcessRecord = ProcessInfo; +export type SandboxProcessRecord = SandboxProcessSnapshot; export interface ActorConn { on(event: string, listener: (payload: any) => void): () => void; diff --git a/foundry/packages/client/src/mock/workspace-client.ts b/foundry/packages/client/src/mock/workspace-client.ts index bf997c9..c51b2e8 100644 --- a/foundry/packages/client/src/mock/workspace-client.ts +++ b/foundry/packages/client/src/mock/workspace-client.ts @@ -142,6 +142,7 @@ class MockWorkspaceStore implements TaskWorkspaceClient { updatedAtMs: nowMs(), pullRequest: { number: nextPrNumber, + status: "ready", title: task.title, state: "open", url: `https://example.test/pr/${nextPrNumber}`, diff --git a/foundry/packages/client/src/workspace-model.ts b/foundry/packages/client/src/workspace-model.ts index 512f852..290794b 100644 --- a/foundry/packages/client/src/workspace-model.ts +++ b/foundry/packages/client/src/workspace-model.ts @@ -198,6 +198,7 @@ function buildPullRequestSummary(params: { }) { return { number: params.number, + status: params.status, title: params.title, state: "open", url: `https://github.com/${params.repoName}/pull/${params.number}`, diff --git a/foundry/packages/client/test/e2e/full-integration-e2e.test.ts b/foundry/packages/client/test/e2e/full-integration-e2e.test.ts index d3851c0..21eaf6b 100644 --- a/foundry/packages/client/test/e2e/full-integration-e2e.test.ts +++ b/foundry/packages/client/test/e2e/full-integration-e2e.test.ts @@ -132,11 +132,11 @@ describe("e2e(client): full integration stack workflow", () => { 90_000, 1_000, async () => client.getRepoOverview(organizationId, repo.repoId), - (value) => value.branches.some((row) => row.branchName === seededBranch), + (value) => value.branches.some((row: RepoOverview["branches"][number]) => row.branchName === seededBranch), ); const postActionOverview = await client.getRepoOverview(organizationId, repo.repoId); - const seededRow = postActionOverview.branches.find((row) => row.branchName === seededBranch); + const seededRow = postActionOverview.branches.find((row: RepoOverview["branches"][number]) => row.branchName === seededBranch); expect(Boolean(seededRow)).toBe(true); expect(postActionOverview.fetchedAt).toBeGreaterThanOrEqual(overview.fetchedAt); } finally { diff --git a/foundry/packages/client/test/e2e/workspace-e2e.test.ts b/foundry/packages/client/test/e2e/workspace-e2e.test.ts index 6eb0f69..1de2065 100644 --- a/foundry/packages/client/test/e2e/workspace-e2e.test.ts +++ b/foundry/packages/client/test/e2e/workspace-e2e.test.ts @@ -176,27 +176,32 @@ describe("e2e(client): workspace flows", () => { expect(transcriptIncludesAgentText(findTab(initialCompleted, primaryTab.id).transcript, expectedInitialReply)).toBe(true); await client.renameWorkspaceTask(organizationId, { + repoId: repo.repoId, taskId: created.taskId, value: `Workspace E2E ${runId} Renamed`, }); await client.renameWorkspaceSession(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: primaryTab.id, title: "Primary Session", }); const secondTab = await client.createWorkspaceSession(organizationId, { + repoId: repo.repoId, taskId: created.taskId, model, }); await client.renameWorkspaceSession(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: secondTab.sessionId, title: "Follow-up Session", }); await client.updateWorkspaceDraft(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: secondTab.sessionId, text: [ @@ -219,6 +224,7 @@ describe("e2e(client): workspace flows", () => { expect(findTab(drafted, secondTab.sessionId).draft.attachments).toHaveLength(1); await client.sendWorkspaceMessage(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: secondTab.sessionId, text: [ @@ -254,16 +260,18 @@ describe("e2e(client): workspace flows", () => { expect(withSecondReply.fileChanges.some((file) => file.path === expectedFile)).toBe(true); await client.setWorkspaceSessionUnread(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: secondTab.sessionId, unread: false, }); - await client.markWorkspaceUnread(organizationId, { taskId: created.taskId }); + await client.markWorkspaceUnread(organizationId, { repoId: repo.repoId, taskId: created.taskId }); const unreadSnapshot = findTask(await client.getWorkspace(organizationId), created.taskId); expect(unreadSnapshot.sessions.some((tab) => tab.unread)).toBe(true); await client.closeWorkspaceSession(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: secondTab.sessionId, }); @@ -278,6 +286,7 @@ describe("e2e(client): workspace flows", () => { expect(closedSnapshot.sessions).toHaveLength(1); await client.revertWorkspaceFile(organizationId, { + repoId: repo.repoId, taskId: created.taskId, path: expectedFile, }); diff --git a/foundry/packages/client/test/e2e/workspace-load-e2e.test.ts b/foundry/packages/client/test/e2e/workspace-load-e2e.test.ts index 726fa8c..f9fc244 100644 --- a/foundry/packages/client/test/e2e/workspace-load-e2e.test.ts +++ b/foundry/packages/client/test/e2e/workspace-load-e2e.test.ts @@ -245,12 +245,14 @@ describe("e2e(client): workspace load", () => { const expectedReply = `LOAD_REPLY_${runId}_${sessionIndex}`; const createSessionStartedAt = performance.now(); const createdSession = await client.createWorkspaceSession(organizationId, { + repoId: repo.repoId, taskId: created.taskId, model, }); createSessionLatencies.push(performance.now() - createSessionStartedAt); await client.sendWorkspaceMessage(organizationId, { + repoId: repo.repoId, taskId: created.taskId, sessionId: createdSession.sessionId, text: `Run pwd in the repo, then reply with exactly: ${expectedReply}`, diff --git a/foundry/packages/shared/src/contracts.ts b/foundry/packages/shared/src/contracts.ts index 4fee165..07eb34c 100644 --- a/foundry/packages/shared/src/contracts.ts +++ b/foundry/packages/shared/src/contracts.ts @@ -60,6 +60,7 @@ export type CreateTaskInput = z.infer; export const WorkspacePullRequestSummarySchema = z.object({ number: z.number().int(), + status: z.enum(["draft", "ready"]), title: z.string().min(1), state: z.string().min(1), url: z.string().min(1), diff --git a/foundry/packages/shared/src/logging.ts b/foundry/packages/shared/src/logging.ts index fa4b739..12e9abc 100644 --- a/foundry/packages/shared/src/logging.ts +++ b/foundry/packages/shared/src/logging.ts @@ -1,4 +1,4 @@ -import { pino, type Logger, type LoggerOptions } from "pino"; +import { pino, type LogFn, type Logger, type LoggerOptions } from "pino"; export interface FoundryLoggerOptions { service: string; @@ -160,7 +160,7 @@ export function createFoundryLogger(options: FoundryLoggerOptions): Logger { loggerOptions.timestamp = pino.stdTimeFunctions.isoTime; if (options.format === "logfmt") { loggerOptions.hooks = { - logMethod(this: Logger, args, _method, level) { + logMethod(this: Logger, args: Parameters, _method: LogFn, level: number) { const levelLabel = this.levels.labels[level] ?? "info"; const record = buildLogRecord(levelLabel, this.bindings(), args); writeLogfmtLine(formatLogfmtLine(record));