import { expect, test, describe, beforeAll, afterAll } from "bun:test";
import { spawn } from "bun";
import { join } from "path";
import { tmpdir } from "os";
import { mkdir, writeFile, rm } from "fs/promises";
/**
* Smoke tests for piping between .md agent files.
* Uses MA_COMMAND=echo to simulate LLM responses without actual API calls.
* These tests verify the stdin/stdout piping mechanism works correctly.
*/
describe("smoke: pipe between agents", () => {
const testDir = join(tmpdir(), `ma-smoke-pipe-${Date.now()}`);
const indexPath = join(process.cwd(), "src/index.ts");
beforeAll(async () => {
await mkdir(testDir, { recursive: true });
});
afterAll(async () => {
await rm(testDir, { recursive: true, force: true });
});
test("stdin is passed to agent and wrapped in tags", async () => {
// Agent that just echoes its body (which includes stdin)
const agentFile = join(testDir, "echo-stdin.echo.md");
await writeFile(agentFile, `---
---
Process this input:
`);
const proc = spawn({
cmd: ["bash", "-c", `echo "hello world" | bun run ${indexPath} ${agentFile}`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
expect(output).toContain("Process this input:");
expect(output).toContain("");
expect(output).toContain("hello world");
expect(output).toContain("");
});
test("pipe: agent1 | agent2 (two-stage pipeline)", async () => {
// Stage 1: Transforms input to structured output
const agent1 = join(testDir, "stage1.echo.md");
await writeFile(agent1, `---
---
STAGE1_OUTPUT: processed
`);
// Stage 2: Receives stage 1 output
const agent2 = join(testDir, "stage2.echo.md");
await writeFile(agent2, `---
---
STAGE2_RECEIVED:
`);
const proc = spawn({
cmd: ["bash", "-c", `echo "initial" | bun run ${indexPath} ${agent1} | bun run ${indexPath} ${agent2}`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
// Stage 2 output should contain its body
expect(output).toContain("STAGE2_RECEIVED:");
// Stage 2 should have received Stage 1's output in stdin
expect(output).toContain("");
expect(output).toContain("STAGE1_OUTPUT: processed");
});
test("pipe: agent1 | agent2 | agent3 (three-stage pipeline)", async () => {
const agent1 = join(testDir, "three-stage1.echo.md");
await writeFile(agent1, `---
---
[STEP1]
`);
const agent2 = join(testDir, "three-stage2.echo.md");
await writeFile(agent2, `---
---
[STEP2]
`);
const agent3 = join(testDir, "three-stage3.echo.md");
await writeFile(agent3, `---
---
[STEP3_FINAL]
`);
const proc = spawn({
cmd: ["bash", "-c", `echo "start" | bun run ${indexPath} ${agent1} | bun run ${indexPath} ${agent2} | bun run ${indexPath} ${agent3}`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
// Final output is from stage 3
expect(output).toContain("[STEP3_FINAL]");
// Stage 3 received stage 2's output (which included stage 1's output)
expect(output).toContain("[STEP2]");
// The chain preserved earlier outputs in stdin
expect(output).toContain("[STEP1]");
});
test("template vars work in piped context", async () => {
const agent = join(testDir, "template-pipe.echo.md");
await writeFile(agent, `---
args: [name]
---
Hello {{ name }}!
`);
const proc = spawn({
cmd: ["bash", "-c", `echo "context" | bun run ${indexPath} ${agent} "World"`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
expect(output).toContain("Hello World!");
expect(output).toContain("");
});
test("frontmatter flags are passed correctly in pipe", async () => {
const agent = join(testDir, "flags-pipe.echo.md");
await writeFile(agent, `---
model: test-model
verbose: true
---
Body content
`);
// Use a wrapper to capture what args echo receives
const proc = spawn({
cmd: ["bash", "-c", `echo "input" | bun run ${indexPath} ${agent} --dry-run 2>&1`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
// Dry run shows the command that would be executed
expect(output).toContain("--model");
expect(output).toContain("test-model");
});
test("empty stdin is handled gracefully", async () => {
const agent = join(testDir, "empty-stdin.echo.md");
await writeFile(agent, `---
---
No stdin expected
`);
const proc = spawn({
cmd: ["bash", "-c", `bun run ${indexPath} ${agent}`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
expect(output).toContain("No stdin expected");
// No stdin tags when there's no stdin
expect(output).not.toContain("");
});
test("multiline stdin is preserved through pipe", async () => {
const agent = join(testDir, "multiline.echo.md");
await writeFile(agent, `---
---
Received:
`);
const multilineInput = "line1\nline2\nline3";
const proc = spawn({
cmd: ["bash", "-c", `printf "${multilineInput}" | bun run ${indexPath} ${agent}`],
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, MA_COMMAND: "echo" },
});
const output = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
expect(exitCode).toBe(0);
expect(output).toContain("line1");
expect(output).toContain("line2");
expect(output).toContain("line3");
});
});