fix(fleet): harden operator sends for release (#565)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful

This commit was merged in pull request #565.
This commit is contained in:
2026-06-20 20:41:11 +00:00
parent 6dfd78f643
commit b5c1381e45
4 changed files with 236 additions and 11 deletions

View File

@@ -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']));
});
});