Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
945a6ba
docs: design plan for git-native results storage (#1259)
christso May 21, 2026
22caf9a
feat(results): Pass 1 — config schema + path renames
christso May 21, 2026
87edfec
fix(results): fix lint + update resolveResultsRepoRunsDir + serve tests
christso May 21, 2026
75d680c
wip: initial git-native listing skeleton + implementation goal
christso May 21, 2026
3b57b7f
fix: remove duplicate execFileAsync declaration
christso May 21, 2026
f5a04ba
feat(results): improve git-native listing metadata shape
christso May 21, 2026
0dba079
chore: update implementation goal + docker ownership fix
christso May 22, 2026
d124456
fix(results): restore git-native run listing
christso May 22, 2026
053d04b
chore(results): satisfy lint
christso May 22, 2026
a6ffd14
fix(test): stabilize git subprocess checks
christso May 22, 2026
1f4a2ff
chore(test): satisfy lint and timeouts
christso May 22, 2026
9cc923d
feat(results): finish git-native results flow
christso May 22, 2026
dab89e0
fix(results): complete remote-only studio flow
christso May 22, 2026
c8781a2
seed repo
May 22, 2026
e7c245e
fix(test): isolate git env in serve regression
May 22, 2026
77c306d
fix(test): restore readme after temp repo setup
May 22, 2026
c434837
fix(test): trim low-value flaky coverage
christso May 22, 2026
8b9e112
fix(results): materialize synced remote runs
christso May 22, 2026
4db04c9
fix(results): atomically materialize synced runs
christso May 22, 2026
1bfd918
docs(studio): clarify remote results behavior
christso May 23, 2026
39f62a6
fix(cli): treat AGENTV_HOME log as info
christso May 23, 2026
8812a64
docs(studio): refresh remote results screenshots
christso May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 76 additions & 21 deletions apps/cli/src/commands/results/remote.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { existsSync } from 'node:fs';
import path from 'node:path';

import {
Expand All @@ -8,7 +9,10 @@ import {
directPushResults,
directorySizeBytes,
getResultsRepoStatus,
listGitRuns,
loadConfig,
materializeGitRun,
normalizeResultsConfig,
resolveResultsRepoRunsDir,
syncResultsRepo,
} from '@agentv/core';
Expand Down Expand Up @@ -59,15 +63,6 @@ function getStatusMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

function normalizeResultsConfig(config: ResultsConfig): Required<ResultsConfig> {
return {
repo: config.repo,
path: config.path,
auto_push: config.auto_push === true,
branch_prefix: config.branch_prefix?.trim() || 'eval-results',
};
}

function statusForResult(result: EvaluationResult): 'PASS' | 'FAIL' | 'ERROR' {
if (result.executionStatus === 'execution_error' || result.error) {
return 'ERROR';
Expand Down Expand Up @@ -131,10 +126,14 @@ export function decodeRemoteRunId(filename: string): string {
export async function getRemoteResultsStatus(cwd: string): Promise<RemoteResultsStatus> {
const config = await loadNormalizedResultsConfig(cwd);
const status = getResultsRepoStatus(config);
const runCount =
config && status.available
? listResultFilesFromRunsDir(resolveResultsRepoRunsDir(config)).length
: 0;
let runCount = 0;
if (config && status.available) {
try {
runCount = (await listGitRuns(config.path)).length;
} catch {
runCount = listResultFilesFromRunsDir(resolveResultsRepoRunsDir(config)).length;
}
}
return {
...status,
run_count: runCount,
Expand Down Expand Up @@ -185,15 +184,45 @@ export async function listMergedResultFiles(
};
}

const remoteRuns = listResultFilesFromRunsDir(resolveResultsRepoRunsDir(config)).map(
(meta) =>
({
...meta,
filename: encodeRemoteRunId(meta.filename),
raw_filename: meta.filename,
let remoteRuns: SourcedResultFileMeta[] = [];
if (config.mode === 'github') {
try {
const gitRuns = await listGitRuns(config.path);
remoteRuns = gitRuns.map((r) => ({
filename: encodeRemoteRunId(r.run_id),
raw_filename: r.run_id,
source: 'remote' as const,
}) satisfies SourcedResultFileMeta,
);
path: path.join(config.path, r.manifest_path),
displayName: r.display_name,
timestamp: r.timestamp,
testCount: r.test_count,
passRate: r.pass_rate || 0,
avgScore: r.avg_score || 0,
sizeBytes: r.size_bytes || 0,
}));
} catch (error) {
console.error('git-native listing failed, falling back', error);
remoteRuns = listResultFilesFromRunsDir(resolveResultsRepoRunsDir(config)).map(
(meta) =>
({
...meta,
filename: encodeRemoteRunId(meta.filename),
raw_filename: meta.filename,
source: 'remote' as const,
}) satisfies SourcedResultFileMeta,
);
}
} else {
remoteRuns = listResultFilesFromRunsDir(resolveResultsRepoRunsDir(config)).map(
(meta) =>
({
...meta,
filename: encodeRemoteRunId(meta.filename),
raw_filename: meta.filename,
source: 'remote' as const,
}) satisfies SourcedResultFileMeta,
);
}

const merged = [...localRuns, ...remoteRuns].sort((a, b) =>
b.timestamp.localeCompare(a.timestamp),
Expand All @@ -212,6 +241,32 @@ export async function findRunById(
return runs.find((run) => run.filename === runId);
}

export async function ensureRemoteRunAvailable(
cwd: string,
meta: Pick<SourcedResultFileMeta, 'source' | 'path'>,
): Promise<void> {
if (meta.source !== 'remote' || existsSync(meta.path)) {
return;
}

const config = await loadNormalizedResultsConfig(cwd);
if (!config) {
throw new Error('Remote results are not configured');
}

const relativeManifestPath = path.relative(config.path, meta.path).split(path.sep).join('/');
if (
relativeManifestPath.length === 0 ||
relativeManifestPath === meta.path ||
relativeManifestPath.startsWith('../')
) {
throw new Error(`Remote manifest path is outside the results repo clone: ${meta.path}`);
}

const relativeRunPath = path.posix.relative('runs', path.posix.dirname(relativeManifestPath));
await materializeGitRun(config.path, relativeRunPath);
}

export async function maybeAutoExportRunArtifacts(payload: RemoteExportPayload): Promise<void> {
const config = await loadNormalizedResultsConfig(payload.cwd);
if (!config?.auto_push) {
Expand Down
Loading
Loading