fix(fleet): consume model_hint + fix socket-default trap (stand-up fixes) (#627)
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:
@@ -117,10 +117,26 @@ export interface FleetPaths {
|
||||
|
||||
type FleetServiceAction = 'start' | 'stop' | 'restart' | 'status';
|
||||
|
||||
const DEFAULT_SOCKET_NAME = 'mosaic-factory';
|
||||
/**
|
||||
* The named tmux socket the canonical fleet uses. Kept as a public constant for
|
||||
* rosters/callers that explicitly want isolation; it is NO LONGER the silent
|
||||
* fallback for a socket-less roster (that now resolves to the default socket).
|
||||
*/
|
||||
export const DEFAULT_SOCKET_NAME = 'mosaic-factory';
|
||||
const DEFAULT_HOLDER_SESSION = '_holder';
|
||||
const DEFAULT_WORKING_DIRECTORY = '~/src';
|
||||
|
||||
/**
|
||||
* tmux `-L` args for a socket name. An empty/absent socket ⇒ the LITERAL default
|
||||
* tmux socket (no `-L`), so spawn, observe (`fleet ps`/watch), and the onboarding
|
||||
* cheat-sheet all agree. A named socket ⇒ `-L <name>`. `DEFAULT_SOCKET_NAME`
|
||||
* remains a constant for callers that explicitly want mosaic-factory; it is no
|
||||
* longer the silent fallback for a socket-less roster.
|
||||
*/
|
||||
export function socketArgs(socketName: string): string[] {
|
||||
return socketName ? ['-L', socketName] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default poll interval (ms) between capture-pane checks in `send --verify`.
|
||||
* Kept short enough to react quickly while not hammering tmux on busy hosts.
|
||||
@@ -185,6 +201,10 @@ export function generateAgentEnv(roster: FleetRoster, agent: FleetAgent): string
|
||||
return [
|
||||
`MOSAIC_AGENT_NAME=${shellEnvValue(agent.name)}`,
|
||||
`MOSAIC_AGENT_RUNTIME=${shellEnvValue(agent.runtime)}`,
|
||||
// Per-agent model hint → start-agent-session.sh appends `--model <hint>` to
|
||||
// the `mosaic yolo` launch so workers run on the roster's model (e.g. pi on
|
||||
// openai-codex/gpt-5.5:high). Empty when the agent declares no model_hint.
|
||||
`MOSAIC_AGENT_MODEL=${shellEnvValue(agent.modelHint ?? '')}`,
|
||||
`MOSAIC_AGENT_WORKDIR=${shellEnvValue(expandHome(workingDirectory))}`,
|
||||
`MOSAIC_TMUX_SOCKET=${shellEnvValue(roster.tmux.socketName)}`,
|
||||
'',
|
||||
@@ -319,13 +339,12 @@ export function buildAgentSendCommand(
|
||||
paths: FleetPaths,
|
||||
agentName: string,
|
||||
message: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
socketName = '',
|
||||
sourceLabel = getDefaultOperatorSourceLabel(),
|
||||
): string[] {
|
||||
return [
|
||||
join(paths.tmuxToolsDir, 'agent-send.sh'),
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'-S',
|
||||
sourceLabel,
|
||||
'-s',
|
||||
@@ -344,12 +363,11 @@ export function buildAgentResetCommand(
|
||||
paths: FleetPaths,
|
||||
agentName: string,
|
||||
resetCommand: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
socketName = '',
|
||||
): string[] {
|
||||
return [
|
||||
join(paths.tmuxToolsDir, 'send-message.sh'),
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'-t',
|
||||
`=${agentName}`,
|
||||
'-m',
|
||||
@@ -357,15 +375,10 @@ export function buildAgentResetCommand(
|
||||
];
|
||||
}
|
||||
|
||||
export function buildAgentTailCommand(
|
||||
agentName: string,
|
||||
lines: number,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
): string[] {
|
||||
export function buildAgentTailCommand(agentName: string, lines: number, socketName = ''): string[] {
|
||||
return [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'capture-pane',
|
||||
'-t',
|
||||
`=${agentName}:0.0`,
|
||||
@@ -449,14 +462,10 @@ export function buildSystemdShowCommand(agentName: string): string[] {
|
||||
* Returns the tmux list-panes command for an agent pane.
|
||||
* Format: `#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity}`
|
||||
*/
|
||||
export function buildTmuxListPanesCommand(
|
||||
agentName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
): string[] {
|
||||
export function buildTmuxListPanesCommand(agentName: string, socketName = ''): string[] {
|
||||
return [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'list-panes',
|
||||
'-t',
|
||||
`=${agentName}:0.0`,
|
||||
@@ -470,8 +479,8 @@ export function buildTmuxListPanesCommand(
|
||||
* Format: `tmux -L <socket> list-sessions -F '#{session_name}'`
|
||||
* Used to discover ad-hoc sessions that are not in the roster.
|
||||
*/
|
||||
export function buildTmuxListSessionsCommand(socketName = DEFAULT_SOCKET_NAME): string[] {
|
||||
return ['tmux', '-L', socketName, 'list-sessions', '-F', '#{session_name}'];
|
||||
export function buildTmuxListSessionsCommand(socketName = ''): string[] {
|
||||
return ['tmux', ...socketArgs(socketName), 'list-sessions', '-F', '#{session_name}'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -653,12 +662,11 @@ export function getDefaultTenantAndHost(): { tenant_id: string; host: string } {
|
||||
export function buildAgentWatchCreateViewerCommand(
|
||||
agentName: string,
|
||||
viewerSessionName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
socketName = '',
|
||||
): string[] {
|
||||
return [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'new-session',
|
||||
'-d',
|
||||
'-t',
|
||||
@@ -672,11 +680,8 @@ export function buildAgentWatchCreateViewerCommand(
|
||||
* Builds the interactive attach command for a viewer session (read-only).
|
||||
* Must be run via interactiveRunner (stdio: 'inherit').
|
||||
*/
|
||||
export function buildAgentWatchAttachCommand(
|
||||
viewerSessionName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
): string[] {
|
||||
return ['tmux', '-L', socketName, 'attach', '-r', '-t', viewerSessionName];
|
||||
export function buildAgentWatchAttachCommand(viewerSessionName: string, socketName = ''): string[] {
|
||||
return ['tmux', ...socketArgs(socketName), 'attach', '-r', '-t', viewerSessionName];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -685,9 +690,9 @@ export function buildAgentWatchAttachCommand(
|
||||
*/
|
||||
export function buildAgentWatchKillViewerCommand(
|
||||
viewerSessionName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
socketName = '',
|
||||
): string[] {
|
||||
return ['tmux', '-L', socketName, 'kill-session', '-t', viewerSessionName];
|
||||
return ['tmux', ...socketArgs(socketName), 'kill-session', '-t', viewerSessionName];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -705,11 +710,8 @@ export function buildViewerSessionName(agentName: string): string {
|
||||
*
|
||||
* Kept for backward compatibility only.
|
||||
*/
|
||||
export function buildAgentWatchCommand(
|
||||
agentName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
): string[] {
|
||||
return ['tmux', '-L', socketName, 'attach', '-r', '-t', `=${agentName}`];
|
||||
export function buildAgentWatchCommand(agentName: string, socketName = ''): string[] {
|
||||
return ['tmux', ...socketArgs(socketName), 'attach', '-r', '-t', `=${agentName}`];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -719,13 +721,12 @@ export function buildAgentWatchCommand(
|
||||
*/
|
||||
export function buildAgentVerifyAcceptedCommand(
|
||||
agentName: string,
|
||||
socketName = DEFAULT_SOCKET_NAME,
|
||||
socketName = '',
|
||||
lines = 5,
|
||||
): string[] {
|
||||
return [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'capture-pane',
|
||||
'-t',
|
||||
`=${agentName}:0.0`,
|
||||
@@ -989,8 +990,7 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
const socketName = roster.tmux.socketName;
|
||||
await runChecked(runner, [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'has-session',
|
||||
'-t',
|
||||
`=${roster.tmux.holderSession}:0.0`,
|
||||
@@ -998,8 +998,7 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
for (const agent of roster.agents) {
|
||||
await runChecked(runner, [
|
||||
'tmux',
|
||||
'-L',
|
||||
socketName,
|
||||
...socketArgs(socketName),
|
||||
'has-session',
|
||||
'-t',
|
||||
`=${agent.name}:0.0`,
|
||||
@@ -1370,8 +1369,8 @@ export function registerFleetAgentCommands(
|
||||
getRosterAgent(roster, agent);
|
||||
}
|
||||
const command = agent
|
||||
? ['tmux', '-L', roster.tmux.socketName, 'has-session', '-t', `=${agent}:0.0`]
|
||||
: ['tmux', '-L', roster.tmux.socketName, 'ls'];
|
||||
? ['tmux', ...socketArgs(roster.tmux.socketName), 'has-session', '-t', `=${agent}:0.0`]
|
||||
: ['tmux', ...socketArgs(roster.tmux.socketName), 'ls'];
|
||||
const result = await runner(...splitCommand(command));
|
||||
if (opts.json) {
|
||||
console.log(
|
||||
@@ -1689,9 +1688,12 @@ function normalizeRoster(raw: RawFleetRoster): FleetRoster {
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: {
|
||||
// Absent socket_name ⇒ '' (the literal default tmux socket, no -L) — NOT
|
||||
// mosaic-factory. Shipped presets set socket_name explicitly, so they are
|
||||
// unaffected; only socket-less rosters get default-socket behavior.
|
||||
socketName: stringValue(
|
||||
raw.tmux?.socket_name ?? raw.tmux?.socketName,
|
||||
DEFAULT_SOCKET_NAME,
|
||||
'',
|
||||
'Fleet roster tmux socket_name',
|
||||
),
|
||||
holderSession: stringValue(
|
||||
@@ -1857,6 +1859,12 @@ function expandHome(path: string): string {
|
||||
}
|
||||
|
||||
function shellEnvValue(value: string): string {
|
||||
// Empty ⇒ a bare `VAR=` (unambiguous empty in a systemd EnvironmentFile and
|
||||
// when shell-sourced). Quoting it as '' risks a literal two-char value (e.g.
|
||||
// a tmux socket named "''"), which would defeat the default-socket behavior.
|
||||
if (value === '') {
|
||||
return '';
|
||||
}
|
||||
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user