fix(fleet): consume model_hint + fix socket-default trap (stand-up fixes) (#627)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #627.
This commit is contained in:
2026-06-22 19:18:01 +00:00
committed by jason.woltje
parent 095e19443b
commit 7342415a32
6 changed files with 180 additions and 74 deletions

View File

@@ -15,6 +15,7 @@ import {
buildFleetServiceCommand,
buildSystemdEnableCommand,
buildSystemdDisableCommand,
socketArgs,
buildSystemdShowCommand,
buildTmuxListPanesCommand,
buildTmuxListSessionsCommand,
@@ -114,7 +115,7 @@ describe('fleet roster parsing', () => {
}
});
it('defaults local canary rosters to the isolated mosaic-factory socket', async () => {
it('defaults a socket-less roster to the literal default tmux socket (empty, no -L)', async () => {
cleanup = await tempDir();
const rosterPath = join(cleanup, 'roster.yaml');
await writeFile(
@@ -131,12 +132,55 @@ describe('fleet roster parsing', () => {
const roster = await loadFleetRoster(rosterPath);
expect(roster.tmux.socketName).toBe('mosaic-factory');
expect(roster.tmux.socketName).toBe(''); // absent ⇒ default socket (no -L), not mosaic-factory
expect(roster.tmux.holderSession).toBe('_holder');
expect(roster.agents).toHaveLength(1);
expect(getRosterAgent(roster, 'canary-pi').runtime).toBe('pi');
});
it('socketArgs: named socket → -L <name>; empty → no -L (default socket)', () => {
expect(socketArgs('mosaic-factory')).toEqual(['-L', 'mosaic-factory']);
expect(socketArgs('')).toEqual([]);
});
it('honors an explicit socket_name (renders -L) — containment for shipped presets', async () => {
cleanup = await tempDir();
const rosterPath = join(cleanup, 'roster.yaml');
await writeFile(
rosterPath,
[
'version: 1',
'transport: tmux',
'tmux:',
' socket_name: mosaic-factory',
'agents:',
' - name: canary-pi',
' runtime: pi',
].join('\n'),
);
const roster = await loadFleetRoster(rosterPath);
expect(roster.tmux.socketName).toBe('mosaic-factory');
expect(buildTmuxListSessionsCommand(roster.tmux.socketName)).toContain('-L');
});
it('maps a per-agent model_hint into MOSAIC_AGENT_MODEL', async () => {
cleanup = await tempDir();
const rosterPath = join(cleanup, 'roster.json');
await writeFile(
rosterPath,
JSON.stringify({
version: 1,
transport: 'tmux',
agents: [{ name: 'coder0', runtime: 'pi', model_hint: 'openai-codex/gpt-5.5:high' }],
}),
);
const roster = await loadFleetRoster(rosterPath);
const env = generateAgentEnv(roster, getRosterAgent(roster, 'coder0'));
expect(env).toContain('MOSAIC_AGENT_MODEL=openai-codex/gpt-5.5:high');
// socket-less roster ⇒ a bare empty socket (no quotes), so spawn uses no -L
expect(env).toContain('MOSAIC_TMUX_SOCKET=\n');
});
it('generates deterministic per-agent EnvironmentFile content', async () => {
cleanup = await tempDir();
const rosterPath = join(cleanup, 'roster.json');
@@ -156,6 +200,7 @@ describe('fleet roster parsing', () => {
[
'MOSAIC_AGENT_NAME=coder0',
'MOSAIC_AGENT_RUNTIME=codex',
'MOSAIC_AGENT_MODEL=',
'MOSAIC_AGENT_WORKDIR=/srv/mosaic',
'MOSAIC_TMUX_SOCKET=mosaic-factory',
'',
@@ -355,8 +400,9 @@ describe('fleet command construction', () => {
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'],
// socket-less roster ⇒ default tmux socket (no -L)
['tmux', 'has-session', '-t', '=_holder:0.0'],
['tmux', 'has-session', '-t', '=coder0:0.0'],
]);
} finally {
await rm(home, { recursive: true, force: true });
@@ -637,7 +683,7 @@ describe('fleet command construction', () => {
try {
await program.parseAsync(['node', 'mosaic', 'agent', 'status', 'json-agent']);
expect(calls).toEqual([
['tmux', '-L', 'mosaic-factory', 'has-session', '-t', '=json-agent:0.0'],
['tmux', 'has-session', '-t', '=json-agent:0.0'], // socket-less ⇒ no -L
]);
} finally {
await rm(home, { recursive: true, force: true });
@@ -677,8 +723,6 @@ describe('fleet command construction', () => {
expect(calls).toEqual([
[
join(home, 'tools', 'tmux', 'agent-send.sh'),
'-L',
'mosaic-factory',
'-S',
getDefaultOperatorSourceLabel(),
'-s',
@@ -727,8 +771,6 @@ describe('fleet command construction', () => {
expect(calls).toEqual([
[
join(home, 'tools', 'tmux', 'agent-send.sh'),
'-L',
'mosaic-factory',
'-S',
'lead:manual',
'-s',
@@ -811,9 +853,11 @@ describe('fleet ps — command construction', () => {
]);
});
it('uses DEFAULT_SOCKET_NAME when socket is omitted from list-panes', () => {
it('uses the default tmux socket (no -L) when socket is omitted from list-panes', () => {
const cmd = buildTmuxListPanesCommand('canary-pi');
expect(cmd[2]).toBe('mosaic-factory');
expect(cmd).not.toContain('-L'); // omitted socket ⇒ default socket
expect(cmd[0]).toBe('tmux');
expect(cmd[1]).toBe('list-panes');
});
it('derives heartbeat path under ~/.config/mosaic/fleet/run/', () => {
@@ -1331,8 +1375,9 @@ describe('fleet ps — command sequences issued', () => {
await program.parseAsync(['node', 'mosaic', 'fleet', 'ps']);
expect(calls).toEqual([
buildSystemdShowCommand('coder0'),
buildTmuxListPanesCommand('coder0', 'mosaic-factory'),
buildTmuxListSessionsCommand('mosaic-factory'),
// socket-less roster ⇒ default socket (no -L)
buildTmuxListPanesCommand('coder0'),
buildTmuxListSessionsCommand(),
]);
} finally {
console.log = origLog;
@@ -1353,9 +1398,10 @@ describe('buildTmuxListSessionsCommand', () => {
]);
});
it('uses DEFAULT_SOCKET_NAME when socket is omitted', () => {
it('uses the default tmux socket (no -L) when socket is omitted', () => {
const cmd = buildTmuxListSessionsCommand();
expect(cmd[2]).toBe('mosaic-factory');
expect(cmd).not.toContain('-L');
expect(cmd).toEqual(['tmux', 'list-sessions', '-F', '#{session_name}']);
});
});
@@ -1633,9 +1679,10 @@ describe('agent watch', () => {
]);
});
it('buildAgentWatchCommand (deprecated) still uses DEFAULT_SOCKET_NAME when socket is omitted', () => {
it('buildAgentWatchCommand (deprecated) uses the default tmux socket (no -L) when socket is omitted', () => {
const cmd = buildAgentWatchCommand('canary-pi');
expect(cmd[2]).toBe('mosaic-factory');
expect(cmd).not.toContain('-L'); // omitted socket ⇒ default socket
expect(cmd[0]).toBe('tmux');
expect(cmd).toContain('-r');
});
@@ -1829,9 +1876,10 @@ describe('agent send --verify', () => {
// 3 calls: BEFORE-capture, send, AFTER-capture (pane changed on first poll → accepted immediately)
expect(calls).toHaveLength(3);
expect(calls[0]).toEqual(buildAgentVerifyAcceptedCommand('coder0', 'mosaic-factory', 5));
// socket-less roster ⇒ default socket (no -L)
expect(calls[0]).toEqual(buildAgentVerifyAcceptedCommand('coder0', '', 5));
expect(calls[1]![0]).toContain('agent-send.sh');
expect(calls[2]).toEqual(buildAgentVerifyAcceptedCommand('coder0', 'mosaic-factory', 5));
expect(calls[2]).toEqual(buildAgentVerifyAcceptedCommand('coder0', '', 5));
} finally {
await rm(home, { recursive: true, force: true });
}