diff --git a/apps/cli/src/commands/results/remote.ts b/apps/cli/src/commands/results/remote.ts index 9bc65aefb..756f5d2c4 100644 --- a/apps/cli/src/commands/results/remote.ts +++ b/apps/cli/src/commands/results/remote.ts @@ -6,11 +6,9 @@ import { type EvaluationResult, type GitListedRun, type ResultsConfig, - ResultsRepoRunExistsError, type ResultsRepoStatus, directPushResults, directorySizeBytes, - findResultsRepoRun, getProject, getProjectForPath, getResultsRepoSyncStatus, @@ -29,7 +27,6 @@ import { listResultFiles, listResultFilesFromRunsDir, } from '../inspect/utils.js'; -import { loadManifestResults } from './manifest.js'; import { type RemoteRunTagState, assertWritableResultsRepo, @@ -100,33 +97,6 @@ export interface RemoteResultsStatus extends ResultsRepoStatus { readonly run_count: number; } -export interface LocalRunPublishPreview { - readonly sourceRunId: string; - readonly targetRepo: string; - readonly targetPath: string; - readonly targetRunId: string; - readonly remoteExists: boolean; - readonly replaceRequired: boolean; - readonly canPublish: boolean; - readonly blockReason?: string; - readonly remoteStatus: RemoteResultsStatus; -} - -export interface LocalRunPublishResult extends LocalRunPublishPreview { - readonly published: boolean; - readonly replaced: boolean; -} - -export class LocalRunPublishError extends Error { - readonly status: 400 | 409; - - constructor(message: string, status: 400 | 409 = 400) { - super(message); - this.name = 'LocalRunPublishError'; - this.status = status; - } -} - const REMOTE_RUN_PREFIX = 'remote::'; const SIZE_WARNING_BYTES = 10 * 1024 * 1024; @@ -152,136 +122,6 @@ function getRelativeRunPath(cwd: string, runDir: string): string { return experiment && experiment !== runName ? path.join(experiment, runName) : runName; } -function toPosixPath(p: string): string { - return p.split(path.sep).join('/'); -} - -function getPublishStatusBlockReason(status: RemoteResultsStatus): string | undefined { - if (!status.configured) { - return 'Remote results repo is not configured for this project.'; - } - - if (status.sync_status === 'syncing') { - return 'Project sync is already in progress. Wait for it to finish before publishing a run.'; - } - - if (status.sync_status && status.sync_status !== 'clean') { - return 'Sync Project before publishing a selected run so pending result metadata is handled first.'; - } - - if (status.blocked) { - return status.block_reason ?? 'Remote results repo sync is blocked.'; - } - - return undefined; -} - -function getExperimentFromRunId(runId: string): string { - const separatorIndex = runId.lastIndexOf('::'); - return separatorIndex === -1 ? 'default' : runId.slice(0, separatorIndex); -} - -async function buildLocalRunPublishPreview(params: { - readonly cwd: string; - readonly config: Required; - readonly meta: Pick; - readonly projectId?: string; -}): Promise { - if (params.meta.source !== 'local') { - throw new LocalRunPublishError('Selected run publish is only available for local runs'); - } - - const runDir = path.dirname(params.meta.path); - const relativeRunPath = toPosixPath(getRelativeRunPath(params.cwd, runDir)); - const targetPath = path.posix.join('.agentv/results/runs', relativeRunPath); - const existingRun = await findResultsRepoRun(params.config, params.meta.raw_filename); - const remoteStatus = await getRemoteResultsStatus(params.cwd, params.projectId); - const blockReason = getPublishStatusBlockReason(remoteStatus); - const remoteExists = existingRun !== undefined; - - return { - sourceRunId: params.meta.raw_filename, - targetRepo: params.config.repo, - targetPath, - targetRunId: params.meta.raw_filename, - remoteExists, - replaceRequired: remoteExists, - canPublish: !blockReason && !remoteExists, - ...(blockReason && { blockReason }), - remoteStatus, - relativeRunPath, - }; -} - -export async function previewLocalRunPublish( - cwd: string, - meta: Pick, - projectId?: string, -): Promise { - const config = await loadNormalizedResultsConfig(cwd, projectId); - if (!config) { - throw new LocalRunPublishError('Remote results repo is not configured for this project', 409); - } - - const { relativeRunPath: _relativeRunPath, ...preview } = await buildLocalRunPublishPreview({ - cwd, - config, - meta, - projectId, - }); - return preview; -} - -export async function publishLocalRun(params: { - readonly cwd: string; - readonly meta: Pick; - readonly projectId?: string; - readonly replace?: boolean; -}): Promise { - const config = await loadNormalizedResultsConfig(params.cwd, params.projectId); - if (!config) { - throw new LocalRunPublishError('Remote results repo is not configured for this project', 409); - } - - const preview = await buildLocalRunPublishPreview({ - cwd: params.cwd, - config, - meta: params.meta, - projectId: params.projectId, - }); - if (preview.blockReason) { - throw new LocalRunPublishError(preview.blockReason, 409); - } - if (preview.remoteExists && params.replace !== true) { - throw new ResultsRepoRunExistsError(preview.targetRunId, preview.relativeRunPath); - } - - await maybeWarnLargeArtifact(path.dirname(params.meta.path)); - const results = loadManifestResults(params.meta.path); - const pushed = await directPushResults({ - config, - sourceDir: path.dirname(params.meta.path), - destinationPath: preview.relativeRunPath, - commitMessage: buildCommitTitle({ - cwd: params.cwd, - run_dir: path.dirname(params.meta.path), - test_files: [], - results, - eval_summaries: [], - experiment: getExperimentFromRunId(preview.targetRunId), - }), - replaceExisting: params.replace === true, - }); - - invalidateGitRunsCache(config.path); - const refreshed = await previewLocalRunPublish(params.cwd, params.meta, params.projectId); - return { - ...refreshed, - published: pushed, - replaced: preview.remoteExists && params.replace === true, - }; -} - function buildCommitTitle(payload: RemoteExportPayload): string { const passed = payload.results.filter((result) => result.score >= DEFAULT_THRESHOLD).length; const avgScore = diff --git a/apps/cli/src/commands/results/serve.ts b/apps/cli/src/commands/results/serve.ts index 2aff716ea..75fce66cb 100644 --- a/apps/cli/src/commands/results/serve.ts +++ b/apps/cli/src/commands/results/serve.ts @@ -40,7 +40,6 @@ import { command, flag, number, option, optional, positional, string } from 'cmd import { DEFAULT_CATEGORY, type EvaluationResult, - ResultsRepoRunExistsError, addProject, getProject, loadConfig, @@ -73,16 +72,12 @@ import { resolveResultSourcePath, } from './manifest.js'; import { - LocalRunPublishError, - type LocalRunPublishPreview, type SourcedResultFileMeta, clearRemoteRunTags, ensureRemoteRunAvailable, findRunById, getRemoteResultsStatus, listMergedResultFiles, - previewLocalRunPublish, - publishLocalRun, readRemoteRunTagState, setRemoteRunTags, syncRemoteResults, @@ -367,34 +362,6 @@ function remoteMetadataErrorStatus(error: unknown): 400 | 409 { return 400; } -function localRunPublishToWire( - preview: LocalRunPublishPreview & { published?: boolean; replaced?: boolean }, -) { - return { - source_run_id: preview.sourceRunId, - target_repo: preview.targetRepo, - target_path: preview.targetPath, - target_run_id: preview.targetRunId, - remote_exists: preview.remoteExists, - replace_required: preview.replaceRequired, - can_publish: preview.canPublish, - ...(preview.blockReason && { block_reason: preview.blockReason }), - remote_status: preview.remoteStatus, - ...(preview.published !== undefined && { published: preview.published }), - ...(preview.replaced !== undefined && { replaced: preview.replaced }), - }; -} - -function localRunPublishErrorStatus(error: unknown): 400 | 409 | 500 { - if (error instanceof LocalRunPublishError) { - return error.status; - } - if (error instanceof ResultsRepoRunExistsError) { - return 409; - } - return 500; -} - async function ensureRunReadable( searchDir: string, meta: SourcedResultFileMeta, @@ -1248,52 +1215,6 @@ function validateLocalCompletedRun( return { error: 'Run workspace is outside the local results directory', status: 400 }; } -async function handleRunPublishPreview(c: C, { searchDir, projectId }: DataContext) { - const filename = c.req.param('filename') ?? ''; - const meta = await findRunById(searchDir, filename, projectId); - if (!meta) return c.json({ error: 'Run not found' }, 404); - const safe = validateLocalCompletedRun(searchDir, meta, 'Selected run publish'); - if ('error' in safe) return c.json({ error: safe.error }, safe.status); - - try { - const preview = await previewLocalRunPublish(searchDir, meta, projectId); - return c.json(localRunPublishToWire(preview)); - } catch (err) { - return c.json({ error: (err as Error).message }, localRunPublishErrorStatus(err)); - } -} - -async function handleRunPublish(c: C, { searchDir, projectId }: DataContext) { - const filename = c.req.param('filename') ?? ''; - const meta = await findRunById(searchDir, filename, projectId); - if (!meta) return c.json({ error: 'Run not found' }, 404); - const safe = validateLocalCompletedRun(searchDir, meta, 'Selected run publish'); - if ('error' in safe) return c.json({ error: safe.error }, safe.status); - - let replace = false; - try { - const body = (await c.req.json()) as unknown; - if (body && typeof body === 'object' && !Array.isArray(body)) { - const value = (body as Record).replace; - if (value !== undefined && typeof value !== 'boolean') { - return c.json({ error: 'replace must be a boolean' }, 400); - } - replace = value === true; - } else if (body !== undefined) { - return c.json({ error: 'Invalid payload' }, 400); - } - } catch { - return c.json({ error: 'Invalid JSON' }, 400); - } - - try { - const result = await publishLocalRun({ cwd: searchDir, meta, projectId, replace }); - return c.json(localRunPublishToWire(result)); - } catch (err) { - return c.json({ error: (err as Error).message }, localRunPublishErrorStatus(err)); - } -} - async function handleRunsCombine(c: C, { searchDir, projectId }: DataContext) { let body: unknown; try { @@ -1618,13 +1539,6 @@ export function createApp( } return handleRunsCombine(c, defaultCtx); }); - app.get('/api/runs/:filename/publish', (c) => handleRunPublishPreview(c, defaultCtx)); - app.post('/api/runs/:filename/publish', (c) => { - if (readOnly) { - return c.json({ error: 'Dashboard is running in read-only mode' }, 403); - } - return handleRunPublish(c, defaultCtx); - }); app.put('/api/runs/:filename/tags', (c) => { if (readOnly) { return c.json({ error: 'Dashboard is running in read-only mode' }, 403); @@ -1766,15 +1680,6 @@ export function createApp( } return withProject(c, handleRunsCombine); }); - app.get('/api/projects/:projectId/runs/:filename/publish', (c) => - withProject(c, handleRunPublishPreview), - ); - app.post('/api/projects/:projectId/runs/:filename/publish', (c) => { - if (readOnly) { - return c.json({ error: 'Dashboard is running in read-only mode' }, 403); - } - return withProject(c, handleRunPublish); - }); app.put('/api/projects/:projectId/runs/:filename/tags', (c) => { if (readOnly) { return c.json({ error: 'Dashboard is running in read-only mode' }, 403); diff --git a/apps/cli/test/commands/results/serve.test.ts b/apps/cli/test/commands/results/serve.test.ts index f205ff164..233fba338 100644 --- a/apps/cli/test/commands/results/serve.test.ts +++ b/apps/cli/test/commands/results/serve.test.ts @@ -1055,11 +1055,13 @@ describe('serve app', () => { expect(existsSync(path.join(runDir, 'tags.json'))).toBe(false); }); - it('previews and publishes a selected local run to the configured results repo', async () => { - const { remoteDir, cloneDir } = initializeRemoteRepo(tempDir); + it('loads a local run detail without cloning or fetching the configured results repo', async () => { + const remoteDir = path.join(tempDir, 'results-remote.git'); + git(`git init --bare --initial-branch=main --quiet "${remoteDir}"`, tempDir); + const missingCloneDir = path.join(tempDir, 'missing-results-clone'); const runId = writeLocalRunArtifact( tempDir, - 'selected-publish', + 'local-detail', '2026-03-26T14-00-00-000Z', RESULT_A, ); @@ -1070,63 +1072,30 @@ describe('serve app', () => { `results: mode: github repo: file://${remoteDir} - path: ${cloneDir} + path: ${missingCloneDir} `, ); const app = createApp([], tempDir, tempDir, undefined, { studioDir }); - const previewRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`); - - expect(previewRes.status).toBe(200); - const preview = (await previewRes.json()) as { - source_run_id: string; - target_repo: string; - target_path: string; - remote_exists: boolean; - can_publish: boolean; - }; - expect(preview).toMatchObject({ - source_run_id: runId, - target_repo: `file://${remoteDir}`, - target_path: '.agentv/results/runs/selected-publish/2026-03-26T14-00-00-000Z', - remote_exists: false, - can_publish: true, - }); + const detailRes = await app.request(`/api/runs/${encodeURIComponent(runId)}`); - const publishRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ replace: false }), - }); - - expect(publishRes.status).toBe(200); - const published = (await publishRes.json()) as { - published: boolean; - remote_exists: boolean; - can_publish: boolean; - }; - expect(published).toMatchObject({ - published: true, - remote_exists: true, - can_publish: false, - }); - expect(git(`git --git-dir "${remoteDir}" ls-tree -r --name-only main`, tempDir)).toContain( - '.agentv/results/runs/selected-publish/2026-03-26T14-00-00-000Z/index.jsonl', - ); - }, 20000); + expect(detailRes.status).toBe(200); + const detail = (await detailRes.json()) as { source: string; results: unknown[] }; + expect(detail.source).toBe('local'); + expect(detail.results).toHaveLength(1); + expect(existsSync(missingCloneDir)).toBe(false); + }); - it('blocks selected local run overwrite unless replace is explicit', async () => { - const { remoteDir, cloneDir } = initializeRemoteRepo(tempDir); + it('does not expose an unscoped selected-run publish endpoint', async () => { + const remoteDir = path.join(tempDir, 'results-remote.git'); + git(`git init --bare --initial-branch=main --quiet "${remoteDir}"`, tempDir); + const missingCloneDir = path.join(tempDir, 'missing-results-clone'); const runId = writeLocalRunArtifact( tempDir, - 'replace-publish', + 'no-publish-route', '2026-03-26T15-00-00-000Z', RESULT_A, ); - writeRemoteRunArtifact(cloneDir, 'replace-publish', '2026-03-26T15-00-00-000Z', { - ...RESULT_A, - output: [{ role: 'assistant', content: 'old remote result' }], - }); mkdirSync(path.join(tempDir, '.agentv'), { recursive: true }); writeFileSync( @@ -1134,93 +1103,82 @@ describe('serve app', () => { `results: mode: github repo: file://${remoteDir} - path: ${cloneDir} + path: ${missingCloneDir} `, ); const app = createApp([], tempDir, tempDir, undefined, { studioDir }); const previewRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`); - expect(previewRes.status).toBe(200); - await expect(previewRes.json()).resolves.toMatchObject({ - remote_exists: true, - replace_required: true, - can_publish: false, - }); - - const blockedRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ replace: false }), - }); - expect(blockedRes.status).toBe(409); - const blocked = (await blockedRes.json()) as { error: string }; - expect(blocked.error).toContain('already has run'); - - const replaceRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`, { + const publishRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ replace: true }), }); - expect(replaceRes.status).toBe(200); - await expect(replaceRes.json()).resolves.toMatchObject({ - published: true, - replaced: true, - remote_exists: true, - }); - }, 20000); - it('blocks selected local run publish when project-level results sync is dirty', async () => { - const { remoteDir, cloneDir } = initializeRemoteRepo(tempDir); - const runId = writeLocalRunArtifact( - tempDir, - 'dirty-publish', - '2026-03-26T16-00-00-000Z', - RESULT_A, - ); - const dirtyPath = path.join( - cloneDir, - '.agentv', - 'results', - 'metadata', - 'runs', - 'dirty-publish', - 'tags.json', - ); - mkdirSync(path.dirname(dirtyPath), { recursive: true }); - writeFileSync(dirtyPath, '{"tags":["pending"]}\n'); + expect(previewRes.status).toBe(404); + expect(publishRes.status).toBe(404); + expect(existsSync(missingCloneDir)).toBe(false); + }); - mkdirSync(path.join(tempDir, '.agentv'), { recursive: true }); - writeFileSync( - path.join(tempDir, '.agentv', 'config.yaml'), - `results: - mode: github - repo: file://${remoteDir} - path: ${cloneDir} -`, - ); + it('does not expose a project-scoped selected-run publish endpoint', async () => { + const previousHome = process.env.AGENTV_HOME; + const homeDir = path.join(tempDir, 'agentv-home-no-publish'); + process.env.AGENTV_HOME = homeDir; - const app = createApp([], tempDir, tempDir, undefined, { studioDir }); - const previewRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`); + try { + const remoteDir = path.join(tempDir, 'results-remote.git'); + git(`git init --bare --initial-branch=main --quiet "${remoteDir}"`, tempDir); + const missingCloneDir = path.join(tempDir, 'missing-project-results-clone'); + const projectDir = path.join(tempDir, 'source-project-no-publish'); + const runId = writeLocalRunArtifact( + projectDir, + 'project-no-publish-route', + '2026-03-26T16-00-00-000Z', + RESULT_A, + ); + mkdirSync(homeDir, { recursive: true }); + saveProjectRegistry({ + projects: [ + { + id: 'project-no-publish', + name: 'Project No Publish', + path: projectDir, + results: { + mode: 'github', + repo: `file://${remoteDir}`, + path: missingCloneDir, + autoPush: true, + }, + addedAt: '2026-01-01T00:00:00.000Z', + lastOpenedAt: '2026-01-01T00:00:00.000Z', + }, + ], + }); - expect(previewRes.status).toBe(200); - const preview = (await previewRes.json()) as { - can_publish: boolean; - block_reason?: string; - remote_status: { sync_status?: string }; - }; - expect(preview.can_publish).toBe(false); - expect(preview.remote_status.sync_status).toBe('dirty'); - expect(preview.block_reason).toContain('Sync Project before publishing'); + const app = createApp([], tempDir, tempDir, undefined, { studioDir }); + const previewRes = await app.request( + `/api/projects/project-no-publish/runs/${encodeURIComponent(runId)}/publish`, + ); + const publishRes = await app.request( + `/api/projects/project-no-publish/runs/${encodeURIComponent(runId)}/publish`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ replace: true }), + }, + ); - const publishRes = await app.request(`/api/runs/${encodeURIComponent(runId)}/publish`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ replace: false }), - }); - expect(publishRes.status).toBe(409); - const data = (await publishRes.json()) as { error: string }; - expect(data.error).toContain('Sync Project before publishing'); - }, 20000); + expect(previewRes.status).toBe(404); + expect(publishRes.status).toBe(404); + expect(existsSync(missingCloneDir)).toBe(false); + } finally { + if (previousHome === undefined) { + process.env.AGENTV_HOME = undefined; + } else { + process.env.AGENTV_HOME = previousHome; + } + } + }); }); describe('GET /api/projects/all-runs', () => { diff --git a/apps/dashboard/src/components/PublishRunAction.tsx b/apps/dashboard/src/components/PublishRunAction.tsx deleted file mode 100644 index b481dd8ee..000000000 --- a/apps/dashboard/src/components/PublishRunAction.tsx +++ /dev/null @@ -1,156 +0,0 @@ -/** - * PublishRunAction — secondary local-run workflow for copying one completed - * run into the configured remote results repo. - * - * Project-level Sync Project remains the primary remote workflow. This - * component only appears for completed local runs, previews the destination - * repo/path before publishing, and requires a separate two-step replace action - * when a remote run with the same ID already exists. - */ - -import { useQueryClient } from '@tanstack/react-query'; -import { useState } from 'react'; - -import { publishRunApi, useRunPublishPreview } from '~/lib/api'; - -type RunSource = 'local' | 'remote' | undefined; -type RunStatus = 'starting' | 'running' | 'finished' | 'failed' | undefined; - -export interface PublishRunActionProps { - runId: string; - projectId?: string; - source: RunSource; - status: RunStatus; - isReadOnly: boolean; -} - -export function PublishRunAction({ - runId, - projectId, - source, - status, - isReadOnly, -}: PublishRunActionProps) { - const queryClient = useQueryClient(); - const [busy, setBusy] = useState<'publish' | 'replace' | null>(null); - const [confirmReplace, setConfirmReplace] = useState(false); - const [feedback, setFeedback] = useState(null); - const [error, setError] = useState(null); - const isActiveRun = status === 'starting' || status === 'running'; - const enabled = !isReadOnly && source === 'local' && !isActiveRun; - const preview = useRunPublishPreview(runId, projectId, enabled); - - if (!enabled) return null; - - async function refreshQueries() { - await Promise.all([ - queryClient.invalidateQueries({ queryKey: ['runs'] }), - queryClient.invalidateQueries({ queryKey: ['projects'] }), - queryClient.invalidateQueries({ queryKey: ['remote-status'] }), - queryClient.invalidateQueries({ queryKey: ['runs', runId, 'publish-preview'] }), - ]); - } - - async function publish(replace: boolean) { - setBusy(replace ? 'replace' : 'publish'); - setError(null); - setFeedback(null); - try { - const result = await publishRunApi(runId, { projectId, replace }); - setConfirmReplace(false); - setFeedback( - result.published - ? replace - ? 'Remote run replaced' - : 'Run published' - : 'Remote already matches this run', - ); - await refreshQueries(); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to publish run'); - } finally { - setBusy(null); - } - } - - const data = preview.data; - const disabled = preview.isLoading || busy !== null || !!data?.block_reason; - const title = - data?.block_reason ?? - (data - ? `${data.target_repo}: ${data.target_path}` - : preview.error - ? (preview.error as Error).message - : 'Loading publish target'); - - return ( -
-
- {data?.remote_exists ? ( - confirmReplace ? ( - <> - - - - ) : ( - - ) - ) : ( - - )} -
- {data ? ( -

- {data.target_repo} / {data.target_path} - {data.remote_exists ? ' - remote run exists' : ''} -

- ) : preview.isLoading ? ( -

Checking publish target...

- ) : null} - {data?.block_reason && ( -

{data.block_reason}

- )} - {feedback &&

{feedback}

} - {(error || preview.error) && ( -

- {error ?? (preview.error as Error).message} -

- )} -
- ); -} diff --git a/apps/dashboard/src/components/RunSourceToolbar.tsx b/apps/dashboard/src/components/RunSourceToolbar.tsx index 432c8a0a4..52c5795fb 100644 --- a/apps/dashboard/src/components/RunSourceToolbar.tsx +++ b/apps/dashboard/src/components/RunSourceToolbar.tsx @@ -87,7 +87,7 @@ export function RunSourceToolbar({ title={!syncView.canSync ? syncView.nextAction : undefined} className="rounded-md border border-cyan-800 bg-cyan-950/40 px-3 py-1.5 text-sm font-medium text-cyan-300 transition-colors hover:bg-cyan-900/50 disabled:cursor-not-allowed disabled:opacity-60" > - {syncInFlight ? 'Syncing...' : 'Sync Remote Results'} + {syncInFlight ? 'Syncing...' : 'Sync Project'} ) : null} diff --git a/apps/dashboard/src/lib/api.ts b/apps/dashboard/src/lib/api.ts index 5ed3b702e..80f42394c 100644 --- a/apps/dashboard/src/lib/api.ts +++ b/apps/dashboard/src/lib/api.ts @@ -29,7 +29,6 @@ import type { FileContentResponse, FileTreeResponse, IndexResponse, - LocalRunPublishPreviewResponse, ProjectEntry, ProjectListResponse, RemoteStatusResponse, @@ -306,41 +305,6 @@ export function useRemoteStatus(projectId?: string) { return useQuery(remoteStatusOptions(projectId)); } -export function runPublishPreviewOptions(filename: string, projectId?: string, enabled = true) { - const url = projectId - ? `${projectApiBase(projectId)}/runs/${encodeURIComponent(filename)}/publish` - : `/api/runs/${encodeURIComponent(filename)}/publish`; - return queryOptions({ - queryKey: ['runs', filename, 'publish-preview', projectId ?? ''], - queryFn: () => fetchJson(url), - enabled: !!filename && enabled, - staleTime: 5_000, - }); -} - -export function useRunPublishPreview(filename: string, projectId?: string, enabled = true) { - return useQuery(runPublishPreviewOptions(filename, projectId, enabled)); -} - -export async function publishRunApi( - filename: string, - options?: { projectId?: string; replace?: boolean }, -): Promise { - const url = options?.projectId - ? `${projectApiBase(options.projectId)}/runs/${encodeURIComponent(filename)}/publish` - : `/api/runs/${encodeURIComponent(filename)}/publish`; - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ replace: options?.replace === true }), - }); - if (!res.ok) { - const err = (await res.json()) as { error?: string }; - throw new Error(err.error || `Failed to publish run: ${res.status}`); - } - return res.json() as Promise; -} - /** Default pass threshold matching @agentv/core DEFAULT_THRESHOLD */ export const DEFAULT_PASS_THRESHOLD = 0.8; export const DEFAULT_APP_NAME = 'agentv'; diff --git a/apps/dashboard/src/lib/types.ts b/apps/dashboard/src/lib/types.ts index ec5bb3c72..8a5d0f76a 100644 --- a/apps/dashboard/src/lib/types.ts +++ b/apps/dashboard/src/lib/types.ts @@ -217,20 +217,6 @@ export interface RunTagsResponse { updated_at: string; } -export interface LocalRunPublishPreviewResponse { - source_run_id: string; - target_repo: string; - target_path: string; - target_run_id: string; - remote_exists: boolean; - replace_required: boolean; - can_publish: boolean; - block_reason?: string; - remote_status: RemoteStatusResponse; - published?: boolean; - replaced?: boolean; -} - export interface CombineDuplicateConflict { key: string; test_id: string; diff --git a/apps/dashboard/src/routes/projects/$projectId_/runs/$runId.tsx b/apps/dashboard/src/routes/projects/$projectId_/runs/$runId.tsx index e4a43f242..a33dbc132 100644 --- a/apps/dashboard/src/routes/projects/$projectId_/runs/$runId.tsx +++ b/apps/dashboard/src/routes/projects/$projectId_/runs/$runId.tsx @@ -5,7 +5,6 @@ import { createFileRoute } from '@tanstack/react-router'; import { useState } from 'react'; -import { PublishRunAction } from '~/components/PublishRunAction'; import { ResumeRunActions } from '~/components/ResumeRunActions'; import { RunDetail } from '~/components/RunDetail'; import { RunEvalModal } from '~/components/RunEvalModal'; @@ -105,13 +104,6 @@ function ProjectRunDetailPage() { runStatus={runStatus} /> )} - {runStatus && } {!isReadOnly && !isActiveRun && (