diff --git a/packages/mosaic/src/commands/fleet-launch-command.spec.ts b/packages/mosaic/src/commands/fleet-launch-command.spec.ts index 53891c67..bcf8f1eb 100644 --- a/packages/mosaic/src/commands/fleet-launch-command.spec.ts +++ b/packages/mosaic/src/commands/fleet-launch-command.spec.ts @@ -122,6 +122,24 @@ describe('profile-selected overlay', () => { }); }); +describe('unscaffolded agent names', () => { + it('points an unscaffolded name at mosaic fleet agent new', () => { + const fx = fixture(); + try { + resolveFleetLaunchComposition('ghost', { + systemHome: fx.systemHome, + userHome: fx.userHome, + }); + throw new Error('expected resolution to fail'); + } catch (error: unknown) { + const launchError = error as FleetLaunchError; + expect(launchError.code).toBe('AGENT_NOT_SCAFFOLDED'); + expect(launchError.message).toContain("no such fleet agent 'ghost'"); + expect(launchError.message).toContain('mosaic fleet agent new ghost'); + } + }); +}); + describe('A3 credential validation', () => { it('refuses a symlinked bundle credential file', () => { const fx = fixture(); diff --git a/packages/mosaic/src/commands/fleet-launch-command.ts b/packages/mosaic/src/commands/fleet-launch-command.ts index 30e5f11a..f718a3fb 100644 --- a/packages/mosaic/src/commands/fleet-launch-command.ts +++ b/packages/mosaic/src/commands/fleet-launch-command.ts @@ -10,7 +10,6 @@ import { writeFileSync, type Stats, } from 'node:fs'; -import { homedir } from 'node:os'; import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; import type { Command } from 'commander'; import { @@ -19,6 +18,7 @@ import { type FleetHarnessContext, type RuntimeName, } from './launch.js'; +import { defaultFleetDataHome } from '../fleet/fleet-agent-scaffold.js'; export const FLEET_AGENT_PROFILE_SCHEMA = 1; const PROFILE_KEYS = [ @@ -47,6 +47,7 @@ const CREDENTIAL_FILES: Record = { export type FleetLaunchErrorCode = | 'SCHEMA_TOO_NEW' | 'PROFILE_INVALID' + | 'AGENT_NOT_SCAFFOLDED' | 'COMPOSITION_FAILED' | 'FIRST_AUTH_REFUSAL'; @@ -513,6 +514,12 @@ export function resolveFleetLaunchComposition( throw new FleetLaunchError('PROFILE_INVALID', `invalid fleet agent name: ${name}`); } const agentDir = join(roots.userHome, 'fleet', 'agents', name); + if (lstatIfPresent(agentDir) === undefined) { + throw new FleetLaunchError( + 'AGENT_NOT_SCAFFOLDED', + `no such fleet agent '${name}' — run: mosaic fleet agent new ${name}`, + ); + } assertRealDirectory(agentDir, 'fleet agent directory'); const profilePath = join(agentDir, 'profile.json'); const profileInfo = lstatIfPresent(profilePath); @@ -687,8 +694,7 @@ export function registerFleetLaunchCommand( .allowExcessArguments(true) .action((name: string, opts: { dryRun?: boolean }, command: Command): void => { try { - const userHome = - deps.userHome ?? process.env['MOSAIC_USER_HOME'] ?? join(homedir(), '.mosaic'); + const userHome = deps.userHome ?? defaultFleetDataHome(); const passthrough = command.args.slice(1); const plan = resolveFleetLaunchComposition( name,