fix(fleet): derive pane idle from window activity fallback (#651)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

This commit was merged in pull request #651.
This commit is contained in:
2026-06-24 06:37:45 +00:00
parent ec8dd7ca86
commit 70661e3fab
3 changed files with 109 additions and 15 deletions

View File

@@ -524,7 +524,7 @@ 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}`
* Format: `#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity} #{window_activity} #{session_activity}`
*/
export function buildTmuxListPanesCommand(agentName: string, socketName = ''): string[] {
return [
@@ -534,7 +534,7 @@ export function buildTmuxListPanesCommand(agentName: string, socketName = ''): s
'-t',
`=${agentName}:0.0`,
'-F',
'#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity}',
'#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity} #{window_activity} #{session_activity}',
];
}
@@ -634,8 +634,8 @@ export function parseSystemdShow(output: string): {
}
/**
* Parse the output of `tmux list-panes -F '#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity}'`
* pane_activity is a Unix epoch timestamp (seconds).
* Parse the output of `tmux list-panes -F '#{pane_pid} #{pane_current_command} #{pane_dead} #{pane_activity} #{window_activity} #{session_activity}'`
* Activity fields are Unix epoch timestamps (seconds), ordered most precise to coarsest.
*/
export function parseTmuxListPanes(
output: string,
@@ -645,16 +645,18 @@ export function parseTmuxListPanes(
if (!line) {
return { pid: null, command: null, dead: true, idleSeconds: null };
}
// format: <pid> <command> <dead(0|1)> <activity_epoch>
// format: <pid> <command> <dead(0|1)> <pane_activity> <window_activity> <session_activity>
const parts = line.split(' ');
const pid = parts[0] ? (Number.isFinite(Number(parts[0])) ? Number(parts[0]) : null) : null;
const command = parts[1] ?? null;
const dead = parts[2] === '1';
const activityEpoch = parts[3] ? Number(parts[3]) : NaN;
const idleSeconds =
Number.isFinite(activityEpoch) && activityEpoch > 0
? Math.floor((nowMs - activityEpoch * 1000) / 1000)
: null;
const activityEpoch = parts
.slice(3, 6)
.map((part) => (part ? Number(part) : NaN))
.find((epoch) => Number.isFinite(epoch) && epoch > 0);
const idleSeconds = activityEpoch
? Math.max(0, Math.floor((nowMs - activityEpoch * 1000) / 1000))
: null;
return { pid, command, dead, idleSeconds };
}