From 6e4518de65bc77d46a859f472eccccaa24e942db Mon Sep 17 00:00:00 2001 From: Rui Conti Date: Fri, 4 Sep 2026 15:32:34 -0400 Subject: [PATCH] fix(eve): exclude sandbox assets from workflow discovery Signed-off-by: Rui Conti --- .changeset/tidy-sandbox-workflows.md | 5 +++ docs/sandbox.mdx | 2 ++ ...hored-workflow-modules.integration.test.ts | 32 +++++++++++++++++++ .../authored-workflow-modules.ts | 4 ++- .../workflow-bundle/builder.scenario.test.ts | 17 ++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-sandbox-workflows.md diff --git a/.changeset/tidy-sandbox-workflows.md b/.changeset/tidy-sandbox-workflows.md new file mode 100644 index 0000000000..d3adc2d378 --- /dev/null +++ b/.changeset/tidy-sandbox-workflows.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Exclude sandbox workspace assets from application workflow discovery. Copied source containing workflow or step directives no longer causes build errors or enters the application's workflow bundles. diff --git a/docs/sandbox.mdx b/docs/sandbox.mdx index e1875cbdcc..0197d5fc1d 100644 --- a/docs/sandbox.mdx +++ b/docs/sandbox.mdx @@ -90,6 +90,8 @@ agent/sandbox/ Every file under `workspace/` mirrors into the sandbox cwd with its structure intact, and eve lists the top-level entries to the model in the prompt automatically. `agent/skills/` files are materialized separately under `$HOME/.agents/skills/`, so `agent/sandbox/workspace/skills/...` is an ordinary workspace subtree when you choose to author one. +Files under `sandbox/workspace/` are sandbox assets. eve excludes them from application workflow discovery, so copied source containing `"use workflow"` or `"use step"` does not register application workflows or steps. This also applies to subagents' sandbox workspaces. + ## Overriding the sandbox To add setup, seed files, or pick a backend, author `defineSandbox`. There are two layouts: diff --git a/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.integration.test.ts b/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.integration.test.ts index f1f5b99ae4..86176a4156 100644 --- a/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.integration.test.ts +++ b/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.integration.test.ts @@ -25,6 +25,38 @@ afterEach(async () => { }); describe("discoverAuthoredWorkflowModules", () => { + it.each(["agent", "agent/subagents/researcher", "custom-agent"])( + "skips sandbox workspace source under %s", + async (agentRoot) => { + await write( + `${agentRoot}/sandbox/workspace/eve-source/src/tools/framework/agent.js`, + `export default { execute() { "use workflow"; return 1; } };`, + ); + await write( + `${agentRoot}/sandbox/workspace/eve-source/src/flow.js`, + `import { missing } from "uninstalled-sandbox-dependency"; +export async function copiedWorkflow() { "use workflow"; return missing(); }`, + ); + await write( + `${agentRoot}/sandbox/workspace/eve-source/src/step.js`, + `export async function copiedStep() { "use step"; return 1; }`, + ); + const authoredPaths = await Promise.all( + [ + `${agentRoot}/sandbox/sandbox.ts`, + `${agentRoot}/sandbox/workspace-helper.ts`, + `${agentRoot}/sandbox/workspace-tools/helper.ts`, + `${agentRoot}/workspace/helper.ts`, + ].map((path) => write(path, `export async function helper() { "use step"; return 1; }`)), + ); + + await expect(discoverAuthoredWorkflowModules(appRoot)).resolves.toEqual({ + directiveModules: authoredPaths.sort(), + workflowModules: [], + }); + }, + ); + it("separates workflow modules from step-only modules and skips the rest", async () => { const tool = await write( "agent/tools/deploy.ts", diff --git a/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.ts b/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.ts index 2b6aabc5b8..15634f0e52 100644 --- a/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.ts +++ b/packages/eve/src/internal/workflow-bundle/authored-workflow-modules.ts @@ -1,6 +1,6 @@ import type { Dirent } from "node:fs"; import { readdir, readFile } from "node:fs/promises"; -import { join } from "node:path"; +import { basename, join } from "node:path"; import { isGeneratedWorkflowFile } from "#compiled/@workflow/builders/index.js"; @@ -80,6 +80,8 @@ async function collectSourceFiles(root: string): Promise { for (const entry of entries) { const entryPath = join(directory, entry.name); if (entry.isDirectory()) { + // Seeded sandbox files are assets, including copied application or framework source. + if (entry.name === "workspace" && basename(directory) === "sandbox") continue; if (!isIgnoredDirectory(entry.name)) await visit(entryPath); continue; } diff --git a/packages/eve/src/internal/workflow-bundle/builder.scenario.test.ts b/packages/eve/src/internal/workflow-bundle/builder.scenario.test.ts index 53c5c0e030..1661013043 100644 --- a/packages/eve/src/internal/workflow-bundle/builder.scenario.test.ts +++ b/packages/eve/src/internal/workflow-bundle/builder.scenario.test.ts @@ -764,12 +764,26 @@ describe("WorkflowBundleBuilder", () => { const compiledArtifactsBootstrapPath = join(tempRoot, "compiled-artifacts-bootstrap.mjs"); const toolPath = join(appRoot, "agent", "tools", "deploy.ts"); const stepsPath = join(appRoot, "agent", "lib", "steps.ts"); + const sandboxSourceRoot = join(appRoot, "agent", "sandbox", "workspace", "eve-source"); try { await mkdir(join(appRoot, "agent", "tools"), { recursive: true }); await mkdir(join(appRoot, "agent", "lib"), { recursive: true }); await mkdir(join(appRoot, "node_modules", "vendored"), { recursive: true }); + await mkdir(sandboxSourceRoot, { recursive: true }); await Promise.all([ + writeFile( + join(sandboxSourceRoot, "agent.js"), + 'export default { execute() { "use workflow"; return 1; } };', + ), + writeFile( + join(sandboxSourceRoot, "flow.js"), + 'import { missing } from "uninstalled-sandbox-dependency";\nexport async function copiedWorkflow() { "use workflow"; return missing(); }', + ), + writeFile( + join(sandboxSourceRoot, "step.js"), + 'export async function copiedStep() { "use step"; return 1; }', + ), writeFile(compiledArtifactsBootstrapPath, "export {};\n"), writeFile( flowFilePath, @@ -851,6 +865,7 @@ describe("WorkflowBundleBuilder", () => { expect(stepsSource).toContain("agent/lib/steps.ts"); expect(stepsSource).toContain("agent/lib/run.ts"); expect(stepsSource).not.toContain("vendored"); + expect(stepsSource).not.toContain("eve-source"); const workflowsSource = await readFile(join(outDir, "workflows.mjs"), "utf8"); const encodedChunksMatch = workflowsSource.match( @@ -864,6 +879,8 @@ describe("WorkflowBundleBuilder", () => { expect(workflowCode).toContain("deploy ${service}"); expect(workflowCode).not.toContain("defineWorkflowTool"); expect(workflowCode).not.toContain("node:crypto"); + expect(workflowCode).not.toContain("copiedWorkflow"); + expect(workflowCode).not.toContain("copiedStep"); } finally { await rm(tempRoot, { force: true, recursive: true }); }