feat(fleet): HARNESS-HOMES fleet MVP — profile-driven seat composition and launch (#1209) #1213

Open
mos-dt-0 wants to merge 11 commits from mos-dt-0/stack:feat/wf-fleet-mvp into next
2 changed files with 27 additions and 3 deletions
Showing only changes of commit 0fdcfa0ff4 - Show all commits
@@ -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();
@@ -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<RuntimeName, string> = {
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,