feat(fleet): auto-enable units on install + drift recognizes wrapped runtimes (#583)
This commit was merged in pull request #583.
This commit is contained in:
@@ -10,11 +10,14 @@ import {
|
||||
buildAgentWatchCreateViewerCommand,
|
||||
buildAgentWatchKillViewerCommand,
|
||||
buildAgentVerifyAcceptedCommand,
|
||||
buildEnableLingerCommand,
|
||||
buildFleetServiceCommand,
|
||||
buildSystemdEnableCommand,
|
||||
buildSystemdShowCommand,
|
||||
buildTmuxListPanesCommand,
|
||||
classifySendResult,
|
||||
detectDrift,
|
||||
enableFleetUnits,
|
||||
generateAgentEnv,
|
||||
getDefaultOperatorSourceLabel,
|
||||
getDefaultTenantAndHost,
|
||||
@@ -28,10 +31,12 @@ import {
|
||||
parseTmuxListPanes,
|
||||
registerFleetCommand,
|
||||
resolveFleetPaths,
|
||||
RUNTIME_ACCEPTABLE_COMMANDS,
|
||||
VERIFY_DEFAULT_TIMEOUT_MS,
|
||||
VERIFY_POLL_INTERVAL_MS,
|
||||
type AgentPsRow,
|
||||
type CommandRunner,
|
||||
type FleetRoster,
|
||||
type InteractiveRunner,
|
||||
type SleepFn,
|
||||
} from './fleet.js';
|
||||
@@ -909,6 +914,118 @@ describe('fleet ps — drift detection', () => {
|
||||
it('does NOT flag drift when pane command is null (pane dead)', () => {
|
||||
expect(detectDrift('pi', null)).toBe(false);
|
||||
});
|
||||
|
||||
it('does NOT flag drift when pane=node for wrapped pi agent (mosaic yolo pi)', () => {
|
||||
expect(detectDrift('pi', 'node')).toBe(false);
|
||||
});
|
||||
|
||||
it('does NOT flag drift when pane=node for wrapped codex agent (mosaic yolo codex)', () => {
|
||||
expect(detectDrift('codex', 'node')).toBe(false);
|
||||
});
|
||||
|
||||
it('flags drift when pane=python3 for pi runtime (canary-pi dogfood regression guard)', () => {
|
||||
expect(detectDrift('pi', 'python3')).toBe(true);
|
||||
});
|
||||
|
||||
it('does NOT flag drift when pane=python3 for dogfood runtime', () => {
|
||||
expect(detectDrift('dogfood', 'python3')).toBe(false);
|
||||
});
|
||||
|
||||
it('flags drift for unknown pane command on known runtime', () => {
|
||||
expect(detectDrift('claude', 'bash')).toBe(true);
|
||||
});
|
||||
|
||||
it('RUNTIME_ACCEPTABLE_COMMANDS is exported and contains expected entries', () => {
|
||||
expect(RUNTIME_ACCEPTABLE_COMMANDS['pi']).toContain('node');
|
||||
expect(RUNTIME_ACCEPTABLE_COMMANDS['pi']).not.toContain('python3');
|
||||
expect(RUNTIME_ACCEPTABLE_COMMANDS['dogfood']).toContain('python3');
|
||||
expect(RUNTIME_ACCEPTABLE_COMMANDS['codex']).toContain('node');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fleet install — auto-enable units for boot-survival', () => {
|
||||
it('buildSystemdEnableCommand and buildEnableLingerCommand return correct command arrays', () => {
|
||||
expect(buildSystemdEnableCommand('mosaic-tmux-holder.service')).toEqual([
|
||||
'systemctl',
|
||||
'--user',
|
||||
'enable',
|
||||
'mosaic-tmux-holder.service',
|
||||
]);
|
||||
expect(buildEnableLingerCommand('testuser')).toEqual(['loginctl', 'enable-linger', 'testuser']);
|
||||
});
|
||||
|
||||
it('enables holder and each agent unit via injected runner after install', async () => {
|
||||
const minimalRoster: FleetRoster = {
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: { socketName: 'mosaic-factory', holderSession: '_holder' },
|
||||
defaults: { workingDirectory: '~/src' },
|
||||
runtimes: { codex: { resetCommand: '/clear' } },
|
||||
agents: [{ name: 'coder0', runtime: 'codex', className: 'worker' }],
|
||||
};
|
||||
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
return { stdout: '', stderr: '', exitCode: 0 };
|
||||
};
|
||||
|
||||
await enableFleetUnits(runner, minimalRoster, {});
|
||||
|
||||
expect(calls).toContainEqual(['systemctl', '--user', 'enable', 'mosaic-tmux-holder.service']);
|
||||
expect(calls).toContainEqual(['systemctl', '--user', 'enable', 'mosaic-agent@coder0.service']);
|
||||
});
|
||||
|
||||
it('install still succeeds when systemctl enable returns non-zero (non-fatal)', async () => {
|
||||
const minimalRoster: FleetRoster = {
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: { socketName: 'mosaic-factory', holderSession: '_holder' },
|
||||
defaults: { workingDirectory: '~/src' },
|
||||
runtimes: { codex: { resetCommand: '/clear' } },
|
||||
agents: [{ name: 'coder0', runtime: 'codex', className: 'worker' }],
|
||||
};
|
||||
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
// Simulate systemctl enable failure
|
||||
if (command === 'systemctl' && args.includes('enable')) {
|
||||
return { stdout: '', stderr: 'Unit not found', exitCode: 1 };
|
||||
}
|
||||
return { stdout: '', stderr: '', exitCode: 0 };
|
||||
};
|
||||
|
||||
// Must NOT reject/throw even when enable calls fail
|
||||
await expect(enableFleetUnits(runner, minimalRoster, {})).resolves.toBeUndefined();
|
||||
|
||||
// The enable attempt must have been made
|
||||
expect(calls.some((c) => c.includes('enable'))).toBe(true);
|
||||
});
|
||||
|
||||
it('--no-enable skips all systemctl enable and loginctl linger calls', async () => {
|
||||
const minimalRoster: FleetRoster = {
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: { socketName: 'mosaic-factory', holderSession: '_holder' },
|
||||
defaults: { workingDirectory: '~/src' },
|
||||
runtimes: { codex: { resetCommand: '/clear' } },
|
||||
agents: [{ name: 'coder0', runtime: 'codex', className: 'worker' }],
|
||||
};
|
||||
|
||||
const calls: string[][] = [];
|
||||
const runner: CommandRunner = async (command, args) => {
|
||||
calls.push([command, ...args]);
|
||||
return { stdout: '', stderr: '', exitCode: 0 };
|
||||
};
|
||||
|
||||
await enableFleetUnits(runner, minimalRoster, { enable: false });
|
||||
|
||||
// No calls should include 'enable'
|
||||
expect(calls.every((c) => !c.includes('enable'))).toBe(true);
|
||||
// No loginctl calls at all
|
||||
expect(calls.every((c) => c[0] !== 'loginctl')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fleet ps — tenant and host', () => {
|
||||
|
||||
Reference in New Issue
Block a user