Files
paseo/packages/cli/tests/05-agent-run.test.ts
Mohamed Boudra 0110302b6f fix(daemon): keep lifecycle operations on the selected instance (#4575)
* fix(daemon): keep lifecycle operations on the selected instance

Restart reconstructed launch inputs and address fallback could redirect a home-selected shutdown to another daemon. Let supervisors publish bound endpoints, retain their worker restart contract, and share captured local lifecycle ownership between CLI and Desktop.

* test(daemon): verify bounded desktop readiness and cancellable artifacts

* test(cli): verify reload target hints and durable offline pairing

* test(daemon): align lifecycle fixtures with ownership and targeting

* fix(daemon): preserve reachability when status details time out

Provider discovery can outlast a status request after authentication succeeds. Keep local connection facts separate from unavailable details, while explicit remote queries retain their error outcome.

Migrate CLI and packaged Desktop fixtures to persistent managed configuration and explicit targets. Isolate generic Git observation from forge discovery and reuse disposable CLI homes on Windows.

* test(daemon): observe restart through the worker status RPC

The bounded CLI status observation may omit worker details while an authenticated connection remains healthy. Exercise the app-style restart through its full RPC and retain the replacement observation, with a real slow provider probe covering the distinction.
2026-09-11 08:33:22 +02:00

278 lines
12 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Phase 4: Run Command Tests
*
* Tests the run command - creating and running agents with tasks (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - run --help shows options
* - run requires prompt argument
* - run handles daemon not running
* - run -d flag is accepted
* - run --name flag is accepted
* - run --provider flag is accepted
* - run --mode flag is accepted
* - run --cwd flag is accepted
*/
import assert from "node:assert";
import { getAvailablePort } from "./helpers/network.ts";
import { $ } from "zx";
import { mkdtemp, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
$.verbose = false;
console.log("=== Run Command Tests ===\n");
// Allocate an unused endpoint for connection-error and argument-validation checks.
const port = await getAvailablePort();
const paseoHome = await mkdtemp(join(tmpdir(), "paseo-test-home-"));
const schemaPath = join(paseoHome, "run-output-schema.json");
await writeFile(
schemaPath,
JSON.stringify({
type: "object",
properties: {
status: { type: "string" },
},
required: ["status"],
additionalProperties: false,
}),
);
try {
// Test 1: run --help shows options
{
console.log("Test 1: run --help shows options");
const result = await $`npx paseo run --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "run --help should exit 0");
assert(result.stdout.includes("-d"), "help should mention -d flag");
assert(result.stdout.includes("--background"), "help should mention --background flag");
assert(!result.stdout.includes("--detach"), "help should hide legacy --detach syntax");
assert(result.stdout.includes("--title"), "help should mention --title option");
assert(result.stdout.includes("--provider"), "help should mention --provider option");
assert(result.stdout.includes("--mode"), "help should mention --mode option");
assert(result.stdout.includes("--new-workspace"), "help should mention --new-workspace option");
assert(!result.stdout.includes("--isolation"), "help should not mention --isolation");
assert(result.stdout.includes("--cwd"), "help should mention --cwd option");
assert(result.stdout.includes("--output-schema"), "help should mention --output-schema option");
assert(result.stdout.includes("--host"), "help should mention --host option");
assert(result.stdout.includes("<prompt>"), "help should mention prompt argument");
assert(!result.stdout.includes("--ui"), "help should not mention removed --ui option");
console.log("✓ run --help shows options\n");
}
// Test 2: run requires prompt argument
{
console.log("Test 2: run requires prompt argument");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail without prompt");
const output = result.stdout + result.stderr;
// Commander should complain about missing argument
const hasMissingArg =
output.toLowerCase().includes("missing") ||
output.toLowerCase().includes("required") ||
output.toLowerCase().includes("argument");
assert(hasMissingArg, "error should mention missing argument");
console.log("✓ run requires prompt argument\n");
}
// Test 3: run handles daemon not running
{
console.log("Test 3: run handles daemon not running");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --provider claude "test prompt"`.nothrow();
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, "should fail when daemon not running");
const output = result.stdout + result.stderr;
const hasError =
output.toLowerCase().includes("daemon") ||
output.toLowerCase().includes("connect") ||
output.toLowerCase().includes("cannot");
assert(hasError, "error message should mention connection issue");
console.log("✓ run handles daemon not running\n");
}
// Test 4: run -d flag is accepted
{
console.log("Test 4: run -d flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run -d "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept -d flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run -d flag is accepted\n");
}
// Test 5: run --name flag is accepted
{
console.log("Test 5: run --name flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --name "test-agent" "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --name flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --name flag is accepted\n");
}
// Test 6: run --provider flag is accepted
{
console.log("Test 6: run --provider flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --provider codex "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --provider flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --provider flag is accepted\n");
}
// Test 6b: run --provider provider/model syntax is accepted
{
console.log("Test 6b: run --provider provider/model syntax is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --provider codex/gpt-5.4 "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept provider/model syntax");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --provider provider/model syntax is accepted\n");
}
// Test 7: run --mode flag is accepted
{
console.log("Test 7: run --mode flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --mode bypass "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --mode flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --mode flag is accepted\n");
}
// Test 8: run --cwd flag is accepted
{
console.log("Test 8: run --cwd flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --cwd /tmp "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --cwd flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --cwd flag is accepted\n");
}
// Test 9: run --output-schema flag is accepted
{
console.log("Test 9: run --output-schema flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --output-schema ${schemaPath} "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --output-schema flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --output-schema flag is accepted\n");
}
// Test 10: run --output-schema cannot be used with --background
{
console.log("Test 10: run --output-schema cannot be used with --background");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --background --output-schema ${schemaPath} "test prompt"`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail with --background and --output-schema");
const output = result.stdout + result.stderr;
assert(
output.includes("--output-schema cannot be used with --background"),
"error should explain background incompatibility",
);
console.log("✓ run --output-schema cannot be used with --background\n");
}
// Test 11: -q (quiet) flag is accepted with run
{
console.log("Test 11: -q (quiet) flag is accepted with run");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} -q run -d "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept -q flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ -q (quiet) flag is accepted with run\n");
}
// Test 12: Combined flags work together
{
console.log("Test 12: Combined flags work together");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} -q run -d --name "test-fixer" --provider claude --mode bypass --cwd /tmp "Fix the tests"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept all combined flags");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ Combined flags work together\n");
}
// Test 12b: conflicting provider/model syntax is rejected before connect
{
console.log("Test 12b: conflicting provider/model syntax is rejected");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --provider codex/gpt-5.4 --model gpt-5.5 "test prompt"`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail for conflicting model inputs");
const output = result.stdout + result.stderr;
assert(
output.includes("Conflicting model values provided"),
"should explain conflicting model inputs",
);
console.log("✓ conflicting provider/model syntax is rejected\n");
}
// Test 13: paseo --help shows run command
{
console.log("Test 13: paseo --help shows run command");
const result = await $`npx paseo --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "paseo --help should exit 0");
assert(result.stdout.includes("run"), "help should mention run command");
console.log("✓ paseo --help shows run command\n");
}
// Test 14: run --ui is rejected (flag removed)
{
console.log("Test 14: run --ui is rejected");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --ui "test prompt"`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail for removed --ui flag");
const output = result.stdout + result.stderr;
assert(output.includes("unknown option"), "should report unknown option for --ui");
console.log("✓ run --ui is rejected\n");
}
// Test 15: run --new-workspace is accepted
{
console.log("Test 15: run --new-workspace is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --new-workspace local "test prompt"`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --new-workspace");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ run --new-workspace is accepted\n");
}
// Test 16: run --isolation is rejected (unreleased flag removed)
{
console.log("Test 16: run --isolation is rejected");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} run --isolation local "test prompt"`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail for removed --isolation flag");
const output = result.stdout + result.stderr;
assert(output.includes("unknown option"), "should report unknown option for --isolation");
console.log("✓ run --isolation is rejected\n");
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true });
}
console.log("=== All run tests passed ===");