@@ -861,6 +861,13 @@ const SupervisorConfig = struct {
861861// PID FILE MANAGEMENT
862862// ═══════════════════════════════════════════════════════════════════════════════
863863
864+ const SupervisorPidState = struct {
865+ pid : i32 ,
866+ started_at : i64 ,
867+ heartbeat : i64 ,
868+ restart_count : u32 ,
869+ };
870+
864871fn writePidFile () ! void {
865872 const pid = std .posix .getpid ();
866873 const dir = std .fs .cwd ().makeOpenPath (".trinity/queen" , .{}) catch | err | {
@@ -869,31 +876,85 @@ fn writePidFile() !void {
869876 };
870877 defer dir .close ();
871878
879+ const now = std .time .timestamp ();
872880 var file = try dir .createFile ("supervisor.pid" , .{ .truncate = true });
873881 defer file .close ();
874- var buf : [32 ]u8 = undefined ;
875- const pid_str = std .fmt .bufPrint (& buf , "{d}" , .{pid }) catch return error .InvalidPid ;
876- try file .writeAll (pid_str );
882+ var buf : [256 ]u8 = undefined ;
883+ const json = std .fmt .bufPrint (& buf ,
884+ \\{{"pid":{d},"started_at":{d},"heartbeat":{d},"restart_count":0}}
885+ , .{ pid , now , now }) catch return error .InvalidPid ;
886+ try file .writeAll (json );
877887}
878888
879- fn removePidFile () void {
880- std .fs .cwd ().deleteFile (qt .SUPERVISOR_PID_PATH ) catch {};
889+ fn updateHeartbeat () void {
890+ const dir = std .fs .cwd ().openDir (".trinity/queen" , .{}) catch return ;
891+ defer dir .close ();
892+ var file = dir .openFile ("supervisor.pid" , .{ .mode = .read_write }) catch return ;
893+ defer file .close ();
894+
895+ var read_buf : [256 ]u8 = undefined ;
896+ const n = file .readAll (& read_buf ) catch return ;
897+ const content = read_buf [0.. n ];
898+
899+ var pid : i32 = 0 ;
900+ var started_at : i64 = 0 ;
901+ var restart_count : u32 = 0 ;
902+ var parser = std .mem .splitSequence (u8 , content [1 .. content .len - 1 ], "," );
903+ while (parser .next ()) | pair | {
904+ var kv = std .mem .splitSequence (u8 , pair , ":" );
905+ const key = std .mem .trim (u8 , kv .first (), " \" " );
906+ const val = kv .next () orelse continue ;
907+ if (std .mem .eql (u8 , key , "pid" )) pid = std .fmt .parseInt (i32 , val , 10 ) catch 0 ;
908+ if (std .mem .eql (u8 , key , "started_at" )) started_at = std .fmt .parseInt (i64 , val , 10 ) catch 0 ;
909+ if (std .mem .eql (u8 , key , "restart_count" )) restart_count = std .fmt .parseInt (u32 , val , 10 ) catch 0 ;
910+ }
911+
912+ const now = std .time .timestamp ();
913+ var buf : [256 ]u8 = undefined ;
914+ const json = std .fmt .bufPrint (& buf ,
915+ \\{{"pid":{d},"started_at":{d},"heartbeat":{d},"restart_count":{d}}}
916+ , .{ pid , started_at , now , restart_count }) catch return ;
917+ file .seekTo (0 ) catch return ;
918+ file .setEndPos (0 ) catch return ;
919+ file .writeAll (json ) catch return ;
881920}
882921
883- fn isSupervisorRunning () bool {
884- const file = std .fs .cwd ().openFile (qt .SUPERVISOR_PID_PATH , .{}) catch return false ;
922+ fn readPidState () ? SupervisorPidState {
923+ const file = std .fs .cwd ().openFile (qt .SUPERVISOR_PID_PATH , .{}) catch return null ;
885924 defer file .close ();
886925
887- var buf : [32 ]u8 = undefined ;
888- const n = file .read (& buf ) catch return false ;
889- if (n == 0 ) return false ;
926+ var buf : [256 ]u8 = undefined ;
927+ const n = file .readAll (& buf ) catch return null ;
928+ const content = buf [0.. n ];
929+
930+ if (content .len > 0 and content [0 ] == '{' ) {
931+ var state = SupervisorPidState { .pid = 0 , .started_at = 0 , .heartbeat = 0 , .restart_count = 0 };
932+ var parser = std .mem .splitSequence (u8 , content [1 .. content .len - 1 ], "," );
933+ while (parser .next ()) | pair | {
934+ var kv = std .mem .splitSequence (u8 , pair , ":" );
935+ const key = std .mem .trim (u8 , kv .first (), " \" " );
936+ const val = kv .next () orelse continue ;
937+ if (std .mem .eql (u8 , key , "pid" )) state .pid = std .fmt .parseInt (i32 , val , 10 ) catch 0 ;
938+ if (std .mem .eql (u8 , key , "started_at" )) state .started_at = std .fmt .parseInt (i64 , val , 10 ) catch 0 ;
939+ if (std .mem .eql (u8 , key , "heartbeat" )) state .heartbeat = std .fmt .parseInt (i64 , val , 10 ) catch 0 ;
940+ if (std .mem .eql (u8 , key , "restart_count" )) state .restart_count = std .fmt .parseInt (u32 , val , 10 ) catch 0 ;
941+ }
942+ return state ;
943+ }
890944
891- const pid_str = buf [0.. n ];
892- const pid = std .fmt .parseInt (i32 , pid_str , 10 ) catch return false ;
945+ // Legacy: plain PID number
946+ const pid = std .fmt .parseInt (i32 , content , 10 ) catch return null ;
947+ return SupervisorPidState { .pid = pid , .started_at = 0 , .heartbeat = 0 , .restart_count = 0 };
948+ }
893949
894- // Check if process is running by sending signal 0
895- // Returns void on success (process exists), error on failure
896- std .posix .kill (pid , 0 ) catch return false ;
950+ fn removePidFile () void {
951+ std .fs .cwd ().deleteFile (qt .SUPERVISOR_PID_PATH ) catch {};
952+ }
953+
954+ fn isSupervisorRunning () bool {
955+ const state = readPidState () orelse return false ;
956+ if (state .pid == 0 ) return false ;
957+ std .posix .kill (state .pid , 0 ) catch return false ;
897958 return true ;
898959}
899960
@@ -1667,9 +1728,19 @@ fn showStatus(allocator: Allocator) !void {
16671728 const hours = @divTrunc (uptime , 3600 );
16681729 const minutes = @divTrunc (@mod (uptime , 3600 ), 60 );
16691730
1731+ var heartbeat_str : []const u8 = "N/A" ;
1732+ var hb_buf : [64 ]u8 = undefined ;
1733+ if (readPidState ()) | pid_state | {
1734+ if (pid_state .heartbeat > 0 ) {
1735+ const hb_age = std .time .timestamp () - pid_state .heartbeat ;
1736+ heartbeat_str = std .fmt .bufPrint (& hb_buf , "{d}s ago" , .{hb_age }) catch "N/A" ;
1737+ }
1738+ }
1739+
16701740 print ("\n {s}" ++ qt .E_CROWN ++ " Queen v2 Status{s}\n\n " ++
16711741 " Cycle: {d}\n " ++
16721742 " Uptime: {d}h {d}m\n " ++
1743+ " Heartbeat: {s}\n " ++
16731744 " Build: {s}{s}{s}\n " ++
16741745 " Dirty: {d}\n " ++
16751746 " Issues: {d}\n " ++
@@ -1686,6 +1757,7 @@ fn showStatus(allocator: Allocator) !void {
16861757 state .cycle ,
16871758 hours ,
16881759 minutes ,
1760+ heartbeat_str ,
16891761 if (snap .build_ok ) GREEN else RED ,
16901762 if (snap .build_ok ) "OK" else "FAIL" ,
16911763 RESET ,
0 commit comments