Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/quiet-builds-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

`eve build` now uses invocation-owned compiler, host, Nitro, Workflow, and output workspaces. Concurrent builds can run beside `eve dev`, and failed builds preserve the last successfully published output.
6 changes: 4 additions & 2 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ Run this first when something behaves unexpectedly. It confirms a file was disco
eve build
```

Compiles to `.eve/` and builds the host output, then prints the built output path.
Compiles and bundles in an invocation-owned directory under `.eve/builds/`, then publishes the completed host output and prints its path. Scratch workspaces are removed after success or failure.

Useful artifacts written under `.eve/` (preserved even on partial failure):
Production builds do not write through the stable compiler, host, Nitro, or Workflow files owned by `eve dev`, so builds can run while a local dev server is active. A failed build leaves the last successful `.output/` and agent summary untouched. Concurrent completed builds serialize only the final publication window.

Useful stable artifacts written by inspection and development flows under `.eve/` include:

| Artifact | Description |
| ---------------------------------------------- | ---------------------------------------------------- |
Expand Down
36 changes: 25 additions & 11 deletions packages/eve/src/compiler/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,17 @@ export interface CompileMetadata {
version: typeof COMPILE_METADATA_VERSION;
}

export interface CompilerArtifactLocations {
readonly publishedRoot: string;
readonly writeRoot: string;
}

/**
* Input for writing compiler-owned discovery artifacts.
*/
interface WriteCompilerArtifactsInput {
appRoot: string;
artifactLocations: CompilerArtifactLocations;
diagnostics: readonly DiscoverDiagnostic[];
manifest: AgentSourceManifest;
}
Expand All @@ -106,13 +112,19 @@ interface WriteCompilerArtifactsResult {
paths: CompilerArtifactPaths;
}

/**
* Resolves the compiler-owned artifact paths for one application root.
*/
/** Resolves stable compiler-owned artifact paths for one application root. */
export function resolveCompilerArtifactPaths(appRoot: string): CompilerArtifactPaths {
return resolveCompilerArtifactPathsAt(appRoot, join(resolve(appRoot), ".eve"));
}

function resolveCompilerArtifactPathsAt(
appRoot: string,
artifactsRoot: string,
): CompilerArtifactPaths {
const resolvedAppRoot = resolve(appRoot);
const discoveryDirectoryPath = join(resolvedAppRoot, ".eve", "discovery");
const compileDirectoryPath = join(resolvedAppRoot, ".eve", "compile");
const resolvedArtifactsRoot = resolve(artifactsRoot);
const discoveryDirectoryPath = join(resolvedArtifactsRoot, "discovery");
const compileDirectoryPath = join(resolvedArtifactsRoot, "compile");

return {
appRoot: resolvedAppRoot,
Expand Down Expand Up @@ -186,13 +198,15 @@ export function createCompileMetadata(input: {
};
}

/**
* Writes the compiler-owned discovery artifacts under `.eve/`.
*/
/** Writes compiler-owned artifacts and records their stable published locations. */
export async function writeCompilerArtifacts(
input: WriteCompilerArtifactsInput,
): Promise<WriteCompilerArtifactsResult> {
const paths = resolveCompilerArtifactPaths(input.appRoot);
const paths = resolveCompilerArtifactPathsAt(input.appRoot, input.artifactLocations.writeRoot);
const publishedPaths = resolveCompilerArtifactPathsAt(
input.appRoot,
input.artifactLocations.publishedRoot,
);
const diagnosticsArtifact = createDiscoveryDiagnosticsArtifact(input.diagnostics);
const compiledManifest = await materializeWorkspaceResources({
compileDirectoryPath: paths.compileDirectoryPath,
Expand All @@ -203,15 +217,15 @@ export async function writeCompilerArtifacts(
const diagnosticsArtifactJson = serializeArtifactJson(diagnosticsArtifact);
const moduleMapSource = createCompiledModuleMapSource({
manifest: compiledManifest,
moduleMapPath: paths.moduleMapPath,
moduleMapPath: publishedPaths.moduleMapPath,
});
const metadata = createCompileMetadata({
appRoot: input.appRoot,
diagnosticsArtifactJson,
diagnosticsSummary: diagnosticsArtifact.summary,
discoveryManifestJson,
moduleMapSource,
paths,
paths: publishedPaths,
});
const metadataJson = serializeArtifactJson(metadata);

Expand Down
118 changes: 90 additions & 28 deletions packages/eve/src/compiler/compile-agent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { join } from "node:path";

import type { DiscoverDiagnostic } from "#discover/diagnostics.js";
import { hasDiscoverErrors, summarizeDiscoverDiagnostics } from "#discover/diagnostics.js";
import { discoverAgent } from "#discover/discover-agent.js";
import type { ResolvedDiscoveryProject } from "#discover/project.js";
import { resolveDiscoveryProject } from "#discover/project.js";
import { createDiskProjectSource, type ProjectSource } from "#discover/project-source.js";
import type { AgentSourceManifest } from "#discover/manifest.js";
import {
type CompileMetadata,
type CompilerArtifactLocations,
type CompilerArtifactPaths,
writeCompilerArtifacts,
} from "#compiler/artifacts.js";
Expand Down Expand Up @@ -43,44 +47,109 @@ export interface CompileAgentResult {
export class CompileAgentError extends Error {
readonly result: CompileAgentResult;

constructor(result: CompileAgentResult) {
super(
formatCompileAgentErrorMessage({
diagnostics: result.diagnostics,
diagnosticsPath: result.paths.diagnosticsPath,
}),
);
private constructor(result: CompileAgentResult, message: string) {
super(message);
this.name = "CompileAgentError";
this.result = result;
}

static fromDurableArtifacts(result: CompileAgentResult): CompileAgentError {
const [summary, ...diagnostics] = formatCompileAgentErrorLines(result.diagnostics);
return new CompileAgentError(
result,
[summary, `Diagnostics artifact: ${result.paths.diagnosticsPath}`, ...diagnostics].join("\n"),
);
}

static fromTransientArtifacts(result: CompileAgentResult): CompileAgentError {
return new CompileAgentError(
result,
formatCompileAgentErrorLines(result.diagnostics).join("\n"),
);
}
}

/**
* Runs discovery, writes compiler-owned artifacts, and throws when discovery
* produced errors.
*/
export async function compileAgent(input: CompileAgentInput = {}): Promise<CompileAgentResult> {
const discovered = await discoverAgentForCompilation(input);
const artifactsRoot = join(discovered.project.appRoot, ".eve");
const result = await writeAgentCompilation(discovered, {
publishedRoot: artifactsRoot,
writeRoot: artifactsRoot,
});

return finishAgentCompilation(result, CompileAgentError.fromDurableArtifacts);
}

/**
* Compiles an agent for a production build. Artifacts are written to the
* invocation-owned `writeRoot` (a throwaway build workspace), while the
* metadata and module map record paths under the stable `publishedRoot`
* where publication later installs them — so the recorded paths stay
* relocatable and identical across builds of the same source.
*/
export async function compileAgentInBuildWorkspace(input: {
readonly artifactLocations: CompilerArtifactLocations;
readonly startPath: string;
}): Promise<CompileAgentResult> {
const discovered = await discoverAgentForCompilation({ startPath: input.startPath });
const result = await writeAgentCompilation(discovered, input.artifactLocations);

return finishAgentCompilation(result, CompileAgentError.fromTransientArtifacts);
}

interface DiscoveredAgentCompilation {
readonly diagnostics: DiscoverDiagnostic[];
readonly manifest: AgentSourceManifest;
readonly project: ResolvedDiscoveryProject;
}

async function discoverAgentForCompilation(
input: CompileAgentInput,
): Promise<DiscoveredAgentCompilation> {
const source = input.source ?? createDiskProjectSource();
const project = await resolveDiscoveryProject(input.startPath, { source });
const discoveryResult = await discoverAgent({ ...project, source });
const writtenArtifacts = await writeCompilerArtifacts({
appRoot: project.appRoot,

return {
diagnostics: discoveryResult.diagnostics,
manifest: discoveryResult.manifest,
project,
};
}

async function writeAgentCompilation(
discovered: DiscoveredAgentCompilation,
artifactLocations: CompilerArtifactLocations,
): Promise<CompileAgentResult> {
const writtenArtifacts = await writeCompilerArtifacts({
appRoot: discovered.project.appRoot,
artifactLocations,
diagnostics: discovered.diagnostics,
manifest: discovered.manifest,
});
const result: CompileAgentResult = {
diagnostics: discoveryResult.diagnostics,

return {
diagnostics: discovered.diagnostics,
manifest: writtenArtifacts.compiledManifest,
metadata: writtenArtifacts.metadata,
paths: writtenArtifacts.paths,
project,
project: discovered.project,
};
}

if (hasDiscoverErrors(discoveryResult.diagnostics)) {
throw new CompileAgentError(result);
function finishAgentCompilation(
result: CompileAgentResult,
createError: (result: CompileAgentResult) => CompileAgentError,
): CompileAgentResult {
if (hasDiscoverErrors(result.diagnostics)) {
throw createError(result);
}

reportDiscoverWarnings(discoveryResult.diagnostics);
reportDiscoverWarnings(result.diagnostics);

return result;
}
Expand All @@ -97,31 +166,24 @@ function reportDiscoverWarnings(diagnostics: readonly DiscoverDiagnostic[]): voi
}
}

function formatCompileAgentErrorMessage(input: {
diagnostics: readonly DiscoverDiagnostic[];
diagnosticsPath?: string;
}): string {
const summary = summarizeDiscoverDiagnostics(input.diagnostics);
function formatCompileAgentErrorLines(diagnostics: readonly DiscoverDiagnostic[]): string[] {
const summary = summarizeDiscoverDiagnostics(diagnostics);
const lines: string[] = [
`Discovery failed with ${summary.errors} error(s) and ${summary.warnings} warning(s).`,
];

if (input.diagnosticsPath !== undefined) {
lines.push(`Diagnostics artifact: ${input.diagnosticsPath}`);
}

if (input.diagnostics.length === 0) {
return lines.join("\n");
if (diagnostics.length === 0) {
return lines;
}

lines.push("Discovery diagnostics:");

for (const diagnostic of input.diagnostics) {
for (const diagnostic of diagnostics) {
lines.push(`- ${formatDiagnosticSeverity(diagnostic.severity)}: ${diagnostic.message}`);
lines.push(` source: ${diagnostic.sourcePath}`);
}

return lines.join("\n");
return lines;
}

function formatDiagnosticSeverity(severity: DiscoverDiagnostic["severity"]): string {
Expand Down
41 changes: 38 additions & 3 deletions packages/eve/src/execution/sandbox/prewarm.scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { join } from "node:path";

import { afterEach, describe, expect, it, vi } from "vitest";

import { compileAgent } from "#compiler/compile-agent.js";
import { createNitroArtifactsConfig } from "#internal/nitro/host/artifacts-config.js";
import { compileAgent, compileAgentInBuildWorkspace } from "#compiler/compile-agent.js";
import { resolvePackageSourceFilePath } from "#internal/application/package.js";
import { createDevelopmentNitroArtifactsConfig } from "#internal/nitro/host/artifacts-config.js";
import { publishDevelopmentRuntimeArtifactsSnapshot } from "#internal/nitro/dev-runtime-artifacts.js";
import { resolveNitroCompiledArtifactsSource } from "#internal/nitro/routes/runtime-artifacts.js";
import { useTemporaryDirectories } from "#internal/testing/use-temporary-app-roots.js";
Expand All @@ -13,6 +14,7 @@ import type {
SandboxBackendPrewarmResult,
} from "#public/definitions/sandbox-backend.js";
import { prewarmAppSandboxes } from "#execution/sandbox/prewarm.js";
import { createDiskRuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";

const createScratchDirectory = useTemporaryDirectories();

Expand All @@ -22,6 +24,39 @@ describe("prewarmAppSandboxes", () => {
vi.unstubAllEnvs();
});

it("loads workspace seeds from an invocation-owned compiler directory", async () => {
vi.stubEnv("VERCEL", "1");
vi.stubEnv("VERCEL_DEPLOYMENT_ID", "dpl_isolated_build_prewarm");

const appRoot = await createScenarioAppRoot();
const compilerAppRoot = join(appRoot, ".eve", "builds", "isolated", "compiler");
await compileAgentInBuildWorkspace({
artifactLocations: {
publishedRoot: join(compilerAppRoot, ".eve"),
writeRoot: join(compilerAppRoot, ".eve"),
},
startPath: appRoot,
});
const events = createPrewarmEvents();

await prewarmAppSandboxes({
appRoot,
compiledArtifactsSource: createDiskRuntimeCompiledArtifactsSource(compilerAppRoot, {
moduleMapLoaderPath: resolvePackageSourceFilePath(
"src/internal/authored-module-map-loader.ts",
),
sandboxAppRoot: appRoot,
}),
dispatch: createRecordingDispatch(events),
});

expect(events.seededTemplateCount).toBe(2);
expect([...events.seededFilePaths].sort()).toEqual([
"$HOME/.agents/skills/research/SKILL.md",
"$HOME/.agents/skills/route-weather/SKILL.md",
]);
});

it("prewarms the root and subagent sandbox templates with per-agent skill seeds", async () => {
// Per-sandbox backend resolution falls back to defaultSandbox() when
// an authored sandbox does not declare `backend`. Mark this process
Expand Down Expand Up @@ -69,7 +104,7 @@ describe("prewarmAppSandboxes", () => {
await prewarmAppSandboxes({
appRoot,
compiledArtifactsSource: resolveNitroCompiledArtifactsSource(
createNitroArtifactsConfig({ appRoot, dev: true }),
createDevelopmentNitroArtifactsConfig({ appRoot }),
),
dispatch: createRecordingDispatch(events),
});
Expand Down
Loading
Loading