fix(fleet): harden operator sends for release (#565)
This commit was merged in pull request #565.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
buildAgentSendCommand,
|
||||
buildFleetServiceCommand,
|
||||
generateAgentEnv,
|
||||
getDefaultOperatorSourceLabel,
|
||||
getRosterAgent,
|
||||
loadFleetRoster,
|
||||
registerFleetCommand,
|
||||
@@ -229,10 +230,14 @@ describe('fleet command construction', () => {
|
||||
|
||||
it('builds socket-scoped agent send commands', () => {
|
||||
const paths = resolveFleetPaths('/home/test/.config/mosaic');
|
||||
expect(buildAgentSendCommand(paths, 'coder0', 'hello', 'mosaic-factory')).toEqual([
|
||||
expect(
|
||||
buildAgentSendCommand(paths, 'coder0', 'hello', 'mosaic-factory', 'operator:mosaic-cli'),
|
||||
).toEqual([
|
||||
'/home/test/.config/mosaic/tools/tmux/agent-send.sh',
|
||||
'-L',
|
||||
'mosaic-factory',
|
||||
'-S',
|
||||
'operator:mosaic-cli',
|
||||
'-s',
|
||||
'coder0',
|
||||
'-m',
|
||||
@@ -255,6 +260,36 @@ describe('fleet command construction', () => {
|
||||
expect(calls).toEqual([['systemctl', '--user', 'status', 'mosaic-tmux-holder.service']]);
|
||||
});
|
||||
|
||||
it('verifies liveness with tmux has-session and does not trust systemd active exited', async () => {
|
||||
const home = await tempDir();
|
||||
const rosterPath = join(home, 'fleet', 'roster.yaml');
|
||||
await mkdir(join(home, 'fleet'), { recursive: true });
|
||||
await writeFile(
|
||||
rosterPath,
|
||||
['version: 1', 'transport: tmux', 'agents:', ' - name: coder0', ' runtime: codex'].join(
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
return { stdout: 'active (exited)\n', stderr: '', exitCode: 0 };
|
||||
};
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerFleetCommand(program, { runner, mosaicHome: home });
|
||||
|
||||
try {
|
||||
await program.parseAsync(['node', 'mosaic', 'fleet', 'verify']);
|
||||
expect(calls).toEqual([
|
||||
['tmux', '-L', 'mosaic-factory', 'has-session', '-t', '=_holder:0.0'],
|
||||
['tmux', '-L', 'mosaic-factory', 'has-session', '-t', '=coder0:0.0'],
|
||||
]);
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('writes init output to the explicit roster path', async () => {
|
||||
const home = await tempDir();
|
||||
const rosterPath = join(home, 'custom', 'roster.yaml');
|
||||
@@ -536,6 +571,104 @@ describe('fleet command construction', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('passes a deterministic operator source label for agent sends', async () => {
|
||||
const home = await tempDir();
|
||||
await mkdir(join(home, 'fleet'), { recursive: true });
|
||||
await writeFile(
|
||||
join(home, 'fleet', 'roster.yaml'),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'json-agent', runtime: 'pi' }],
|
||||
}),
|
||||
);
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
return { stdout: '', stderr: '', exitCode: 0 };
|
||||
};
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerAgentCommand(program, { runner, mosaicHome: home });
|
||||
|
||||
try {
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'mosaic',
|
||||
'agent',
|
||||
'send',
|
||||
'json-agent',
|
||||
'--message',
|
||||
'status check',
|
||||
]);
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
join(home, 'tools', 'tmux', 'agent-send.sh'),
|
||||
'-L',
|
||||
'mosaic-factory',
|
||||
'-S',
|
||||
getDefaultOperatorSourceLabel(),
|
||||
'-s',
|
||||
'json-agent',
|
||||
'-m',
|
||||
'status check',
|
||||
],
|
||||
]);
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('allows agent sends to override the source label explicitly', async () => {
|
||||
const home = await tempDir();
|
||||
await mkdir(join(home, 'fleet'), { recursive: true });
|
||||
await writeFile(
|
||||
join(home, 'fleet', 'roster.yaml'),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'coder0', runtime: 'codex' }],
|
||||
}),
|
||||
);
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
return { stdout: '', stderr: '', exitCode: 0 };
|
||||
};
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerAgentCommand(program, { runner, mosaicHome: home });
|
||||
|
||||
try {
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'mosaic',
|
||||
'agent',
|
||||
'send',
|
||||
'coder0',
|
||||
'--message',
|
||||
'handoff',
|
||||
'--source-label',
|
||||
'lead:manual',
|
||||
]);
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
join(home, 'tools', 'tmux', 'agent-send.sh'),
|
||||
'-L',
|
||||
'mosaic-factory',
|
||||
'-S',
|
||||
'lead:manual',
|
||||
'-s',
|
||||
'coder0',
|
||||
'-m',
|
||||
'handoff',
|
||||
],
|
||||
]);
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects agent status typos before invoking the runner', async () => {
|
||||
const home = await tempDir();
|
||||
const rosterPath = join(home, 'fleet', 'roster.yaml');
|
||||
@@ -560,4 +693,14 @@ describe('fleet command construction', () => {
|
||||
await rm(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps fleet framework assets in the published package file list', async () => {
|
||||
const packageJson = JSON.parse(
|
||||
await readFile(resolve(process.cwd(), 'package.json'), 'utf8'),
|
||||
) as {
|
||||
files?: string[];
|
||||
};
|
||||
|
||||
expect(packageJson.files).toEqual(expect.arrayContaining(['dist', 'framework']));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user