feat(fleet): surface self-reported model in fleet ps
parseHeartbeat now reads an optional model= line from the heartbeat file (written by native runtime heartbeats) into HeartbeatInfo.model, and fleet ps surfaces it as a MODEL column (table) and in --json (via rows[].heartbeat.model). Legacy/sidecar heartbeats omit the line and report model=null, so the column shows '-'. Closes the model self-report gap end-to-end with the native Pi heartbeat writer (F3-m2): the runtime self-reports its active model and the fleet operator can see it in ps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
This commit is contained in:
@@ -833,6 +833,17 @@ describe('fleet ps — heartbeat parsing', () => {
|
||||
expect(hb.pid).toBe(12345);
|
||||
expect(hb.status).toBe('ok');
|
||||
expect(hb.ageMs).toBe(10_000);
|
||||
// No model= line in a legacy/sidecar heartbeat → model is null.
|
||||
expect(hb.model).toBeNull();
|
||||
});
|
||||
|
||||
it('parses a self-reported model id from a native heartbeat (model= line)', () => {
|
||||
const ts = new Date(NOW - 5_000).toISOString();
|
||||
const content = `ts=${ts}\npid=42\nstatus=busy\nmodel=openai-codex/gpt-5.5:high\n`;
|
||||
const hb = parseHeartbeat(content, NOW);
|
||||
expect(hb.model).toBe('openai-codex/gpt-5.5:high');
|
||||
expect(hb.status).toBe('busy');
|
||||
expect(hb.health).toBe('healthy');
|
||||
});
|
||||
|
||||
it('reports stale when heartbeat is older than 3×interval', () => {
|
||||
|
||||
@@ -390,6 +390,8 @@ export interface HeartbeatInfo {
|
||||
/** healthy | stale | unknown */
|
||||
health: 'healthy' | 'stale' | 'unknown';
|
||||
ageMs: number | null;
|
||||
/** Model id the runtime self-reported in its heartbeat (native HB only), else null. */
|
||||
model: string | null;
|
||||
}
|
||||
|
||||
export interface AgentPsRow {
|
||||
@@ -490,15 +492,17 @@ export function heartbeatPath(agentName: string, mosaicHome = defaultMosaicHome(
|
||||
* ts=<iso8601>
|
||||
* pid=<pid>
|
||||
* status=<ok|busy>
|
||||
* model=<model-id> (optional — native runtime heartbeats self-report it)
|
||||
*/
|
||||
export function parseHeartbeat(content: string | null, nowMs = Date.now()): HeartbeatInfo {
|
||||
if (content === null) {
|
||||
return { ts: null, pid: null, status: null, health: 'unknown', ageMs: null };
|
||||
return { ts: null, pid: null, status: null, health: 'unknown', ageMs: null, model: null };
|
||||
}
|
||||
const lines = content.split('\n');
|
||||
let ts: Date | null = null;
|
||||
let pid: number | null = null;
|
||||
let status: 'ok' | 'busy' | null = null;
|
||||
let model: string | null = null;
|
||||
for (const line of lines) {
|
||||
const [key, ...rest] = line.split('=');
|
||||
const val = rest.join('=').trim();
|
||||
@@ -510,6 +514,8 @@ export function parseHeartbeat(content: string | null, nowMs = Date.now()): Hear
|
||||
if (Number.isFinite(n)) pid = n;
|
||||
} else if (key === 'status' && (val === 'ok' || val === 'busy')) {
|
||||
status = val;
|
||||
} else if (key === 'model' && val) {
|
||||
model = val;
|
||||
}
|
||||
}
|
||||
const thresholdMs = heartbeatIntervalMs() * HEARTBEAT_HEALTHY_MULTIPLIER;
|
||||
@@ -519,7 +525,7 @@ export function parseHeartbeat(content: string | null, nowMs = Date.now()): Hear
|
||||
ageMs = nowMs - ts.getTime();
|
||||
health = ageMs <= thresholdMs ? 'healthy' : 'stale';
|
||||
}
|
||||
return { ts, pid, status, health, ageMs };
|
||||
return { ts, pid, status, health, ageMs, model };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1123,6 +1129,7 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
'PID'.padEnd(8),
|
||||
'IDLE'.padEnd(8),
|
||||
'HB'.padEnd(12),
|
||||
'MODEL'.padEnd(22),
|
||||
'FLAGS',
|
||||
].join(' ');
|
||||
console.log(header);
|
||||
@@ -1137,6 +1144,7 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
row.heartbeat.ageMs !== null
|
||||
? `${Math.round(row.heartbeat.ageMs / 1000)}s/${row.heartbeat.health}`
|
||||
: `unknown`;
|
||||
const model = row.heartbeat.model ?? '-';
|
||||
const flags: string[] = [];
|
||||
if (!row.managed) flags.push('UNMANAGED');
|
||||
if (row.driftFlag) flags.push('DRIFT');
|
||||
@@ -1153,6 +1161,7 @@ export function registerFleetCommand(program: Command, deps: FleetCommandDeps =
|
||||
pid.padEnd(8),
|
||||
idle.padEnd(8),
|
||||
hbAge.padEnd(12),
|
||||
model.padEnd(22),
|
||||
flags.join(','),
|
||||
].join(' '),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user